From 09e0e46ec2d5bdf7891961f2b16d48874d980df4 Mon Sep 17 00:00:00 2001 From: Neil Brown Date: Tue, 25 Nov 2008 18:06:14 +0000 Subject: [PATCH] Added some comments to my changes to GenericUtils --- common/GenericUtils.hs | 66 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/common/GenericUtils.hs b/common/GenericUtils.hs index dff11a2..224ecd6 100644 --- a/common/GenericUtils.hs +++ b/common/GenericUtils.hs @@ -160,7 +160,47 @@ gmapMFor typeset f = gmapM (each f) Just Miss -> return x Nothing -> return x - +-- | A Route is a way of navigating to a particular node in a tree structure. +-- +-- Let's say that you have some binary tree structure: +-- +-- > data BinTree a = Leaf a | Branch (BinTree a) (BinTree a) +-- +-- Suppose you then have a big binary tree of integers, potentially with duplicate values, +-- and you want to be able to modify a particular integer. You can't modify in-place, +-- because this is a functional language. So you instead want to be able to apply +-- a modify function to the whole tree that really just modifies the particular +-- integer. +-- +-- To do this you can use: +-- +-- > myRoute :: Route Int (BinTree Int) +-- +-- You apply it as follows (for example, to increment the integer): +-- +-- > runIdentity $ routeModify myRoute (return . (+1)) myTree +-- +-- The modifier is monadic because that's usually how we want to use it, but we +-- can use the identity monad as above for pure functions. This will only work +-- if the route is valid on the given tree. +-- +-- Another useful aspect is composition. If your tree was in a tree of trees: +-- +-- > routeToInnerTree :: Route (BinTree Int) (BinTree (BinTree Int)) +-- +-- You could compose this with the earlier route: +-- +-- > routeToInnerTree @-> myRoute :: Route Int (BinTree (BinTree Int)) +-- +-- These routes are a little like zippers, but (in my opinion) easier to use, and +-- tack on to existing code with complex data structures (without needing any code +-- generation). You can either compose routes yourself (as the flow-graph building +-- does) or by using 'gmapMForRoute'. +-- +-- Routes support Eq, Show and Ord. All these instances represent a route as a +-- list of integers: a route-map. [0,2,1] means first child (zero-based), then +-- third child, then first child of the given data-type. Routes are ordered using +-- the standard list ordering (lexicographic) over this representation. data Route inner outer = Route [Int] (forall m. Monad m => (inner -> m inner) -> (outer -> m outer)) instance Eq (Route inner outer) where @@ -172,30 +212,53 @@ instance Ord (Route inner outer) where instance Show (Route inner outer) where show (Route ns _) = "Route " ++ show ns +-- | Gets the integer-list version of a route. See the documentation of 'Route'. routeId :: Route inner outer -> [Int] routeId (Route ns _) = ns +-- | Given an index (zero is the first item), forms a route to that index item +-- in the list. So for example: +-- +-- > runIdentity $ routeModify (routeList 3) (return . (*10)) [0,1,2,3,4,5] == [0,1,2,30,4,5] +-- routeList :: Int -> Route a [a] routeList 0 = Route [0] (\f (x:xs) -> f x >>* (: xs)) routeList n = Route [1] (\f (x:xs) -> f xs >>* (x:)) @-> routeList (n-1) +-- | Applies a monadic modification function using the given route. routeModify :: Monad m => Route inner outer -> (inner -> m inner) -> (outer -> m outer) routeModify (Route _ wrap) = wrap +-- | Given a route, gets the value in the large data structure that is pointed +-- to by that route. routeGet :: Route inner outer -> outer -> inner routeGet route = flip execState undefined . routeModify route (\x -> put x >> return x) +-- | Given a route, sets the value in the large data structure that is pointed +-- to by that route. routeSet :: Route inner outer -> inner -> outer -> outer routeSet route x = runIdentity . routeModify route (const $ return x) +-- | Composes two routes together. The outer-to-mid route goes on the left hand +-- side, and the mid-to-inner goes on the right hand side to form an outer-to-inner +-- route. (@->) :: Route mid outer -> Route inner mid -> Route inner outer (@->) (Route outInds outF) (Route inInds inF) = Route (outInds ++ inInds) (outF . inF) +-- | The identity route. This has various obvious properties: +-- +-- > routeGet routeIdentity == id +-- > routeSet routeIdentity == const +-- > routeModify routeIdentity == id +-- > routeIdentity @-> route == route +-- > route @-> routeIdentity == route routeIdentity :: Route a a routeIdentity = Route [] id +-- | Acts just like gmapMFor, except that it also tells you the route to the node +-- that your generic function is being applied to. gmapMForRoute :: forall m t. (Monad m, Data t) => TypeSet -> (forall s. Data s => (s, Route s t) -> m s) @@ -211,6 +274,7 @@ gmapMForRoute typeset f = gmapMWithRoute (each f) Just Miss -> return x Nothing -> return x +-- A helper for gmapMForRoute that maps over the direct children and supplies routes gmapMWithRoute :: forall a m. (Monad m, Data a) => (forall b. Data b => (b, Route b a) -> m b) -> a -> m a gmapMWithRoute f = gmapFuncs [GM {unGM = f' n} | n <- [0..]]