How do you represent a graph in Haskell?

后端 未结 8 622
星月不相逢
星月不相逢 2020-11-27 09:39

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

相关标签:
8条回答
  • 2020-11-27 10:20

    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.

    0 讨论(0)
  • 2020-11-27 10:26

    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.

    0 讨论(0)
提交回复
热议问题