It\'s easy enough to represent a tree or list in haskell using algebraic data types. But how would you go about typographically representing a graph? It seems that you need
Any discussion of representing graphs in Haskell needs a mention of Andy Gill's data-reify library (here is the paper).
The "tying-the-knot" style representation can be used to make very elegant DSLs (see example below). However, the data structure is of limited use. Gill's library allows you the best of both worlds. You can use a "tying the knot" DSL, but then convert the pointer-based graph into a label-based graph so you can run your algorithms of choice on it.
Here is a simple example:
-- Graph we want to represent:
-- .----> a <----.
-- / \
-- b <------------. \
-- \ \ /
-- `----> c ----> d
-- Code for the graph:
a = leaf
b = node2 a c
c = node1 d
d = node2 a b
-- Yes, it's that simple!
-- If you want to convert the graph to a Node-Label format:
main = do
g <- reifyGraph b --can't use 'a' because not all nodes are reachable
print g
To run the above code you will need the following definitions:
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeFamilies #-}
import Data.Reify
import Control.Applicative
import Data.Traversable
--Pointer-based graph representation
data PtrNode = PtrNode [PtrNode]
--Label-based graph representation
data LblNode lbl = LblNode [lbl] deriving Show
--Convenience functions for our DSL
leaf = PtrNode []
node1 a = PtrNode [a]
node2 a b = PtrNode [a, b]
-- This looks scary but we're just telling data-reify where the pointers are
-- in our graph representation so they can be turned to labels
instance MuRef PtrNode where
type DeRef PtrNode = LblNode
mapDeRef f (PtrNode as) = LblNode <$> (traverse f as)
I want to stress that this is a simplistic DSL, but the sky's the limit! I designed a very featureful DSL, including a nice tree-like syntax for having a node broadcast an initial value to some of its children, and many convenience functions for constructing specific node types. Of course, the Node data type and mapDeRef definitions were much more involved.
As Ben mentioned, cyclic data in Haskell is constructed by a mechanism called "tying the knot". In practice, it means that we write mutually recursive declarations using let
or where
clauses, which works because the mutually recursive parts are lazily evaluated.
Here's an example graph type:
import Data.Maybe (fromJust)
data Node a = Node
{ label :: a
, adjacent :: [Node a]
}
data Graph a = Graph [Node a]
As you can see, we use actual Node
references instead of indirection. Here's how to implement a function that constructs the graph from a list of label associations.
mkGraph :: Eq a => [(a, [a])] -> Graph a
mkGraph links = Graph $ map snd nodeLookupList where
mkNode (lbl, adj) = (lbl, Node lbl $ map lookupNode adj)
nodeLookupList = map mkNode links
lookupNode lbl = fromJust $ lookup lbl nodeLookupList
We take in a list of (nodeLabel, [adjacentLabel])
pairs and construct the actual Node
values via an intermediate lookup-list (which does the actual knot-tying). The trick is that nodeLookupList
(which has the type [(a, Node a)]
) is constructed using mkNode
, which in turn refers back to the nodeLookupList
to find the adjacent nodes.