Tying the Knot with a State monad

后端 未结 5 1881
隐瞒了意图╮
隐瞒了意图╮ 2021-01-30 16:42

I\'m working on a Haskell project that involves tying a big knot: I\'m parsing a serialized representation of a graph, where each node is at some offset into the file, and may r

5条回答
  •  时光说笑
    2021-01-30 17:32

    I had a similar problem recently, but I chose a different approach. A recursive data structure can be represented as a type fixed point on a data type functor. Loading data can be then split into two parts:

    • Load the data into a structure that references other nodes only by some kind of identifier. In the example it's Loader Int (NodeF Int), which constructs a map of values of type NodeF Int Int.
    • Tie the knot by creating a recursive data structure by replacing the identifiers with actual data. In the example the resulting data structures have type Fix (NodeF Int), and they are later converted to Node Int for convenience.

    It's lacking a proper error handling etc., but the idea should be clear from that.

    -- Public Domain
    
    import Control.Monad
    import Data.Map (Map)
    import qualified Data.Map as Map
    import Data.Maybe (fromJust)
    
    -- Fixed point operator on types and catamohism/anamorphism methods
    -- for constructing/deconstructing them:
    
    newtype Fix f = Fix { unfix :: f (Fix f) }
    
    catam :: Functor f => (f a -> a) -> (Fix f -> a)
    catam f = f . fmap (catam f) . unfix
    
    anam :: Functor f => (a -> f a) -> (a -> Fix f)
    anam f = Fix . fmap (anam f) . f
    
    anam' :: Functor f => (a -> f a) -> (f a -> Fix f)
    anam' f = Fix . fmap (anam f)
    
    -- The loader itself
    
    -- A representation of a loader. Type parameter 'k' represents the keys by
    -- which the nodes are represented. Type parameter 'v' represents a functor
    -- data type representing the values.
    data Loader k v = Loader (Map k (v k))
    
    -- | Creates an empty loader.
    empty :: Loader k v
    empty = Loader $ Map.empty
    
    -- | Adds a new node into a loader.
    update :: (Ord k) => k -> v k -> Loader k v -> Loader k v
    update k v = update' k (const v)
    
    -- | Modifies a node in a loader.
    update' :: (Ord k) => k -> (Maybe (v k) -> (v k)) -> Loader k v -> Loader k v
    update' k f (Loader m) = Loader $ Map.insertWith (const (f . Just)) k (f Nothing) $ m
    
    -- | Does the actual knot-tying. Creates a new data structure
    -- where the references to nodes are replaced by the actual data.
    tie :: (Ord k, Functor v) => Loader k v -> Map k (Fix v)
    tie (Loader m) = Map.map (anam' $ \k -> fromJust (Map.lookup k m)) m
    
    
    -- -----------------------------------------------------------------
    -- Usage example:
    
    data NodeF n t = NodeF n [t]
    instance Functor (NodeF n) where
        fmap f (NodeF n xs) = NodeF n (map f xs)
    
    -- A data structure isomorphic to Fix (NodeF n), but easier to work with.
    data Node n = Node n [Node n]
      deriving Show
    -- The isomorphism that does the conversion.
    nodeunfix :: Fix (NodeF n) -> Node n
    nodeunfix = catam (\(NodeF n ts) -> Node n ts)
    
    main :: IO ()
    main = do
        -- Each node description consist of an integer ID and a list of other nodes
        -- it references.
        let lss = 
                [ (1, [4])
                , (2, [1])
                , (3, [2, 1])
                , (4, [3, 2, 1])
                , (5, [5])
                ]
        print lss
        -- Fill a new loader with the data:
        let
            loader = foldr f empty lss
            f (label, dependsOn) = update label (NodeF label dependsOn)
        -- Tie the knot:
        let tied' = tie loader
        -- And convert Fix (NodeF n) into Node n:
        let tied = Map.map nodeunfix tied'
    
        -- For each node print the label of the first node it references
        -- and the count of all referenced nodes.
        print $ Map.map (\(Node n ls@((Node n1 _) : _)) -> (n1, length ls)) tied
    

提交回复
热议问题