help with GMap, dispatch, etc...

Ralf Laemmel Ralf.Laemmel@cwi.nl
Tue, 06 May 2003 02:08:22 +0200


Hal Daume III wrote:
> 
> I'm in the middle of writing a program for transforming haskell modules, a
> large amount of which is essentially renaming the HsNames inside.  It is
> *almost* sufficient for me to simply write a function:
>   transformName :: HsName -> QM HsName
> ...

Ok, here is my attempt.
Indeed, I end up using gmap as you expect. We get a nice
concise traversal at the end.

So let me first state a concrete problem for the sake
of this exercise. Consider the following nested function
definition:

f x y = x o y 
 where
  o x y = x ++ y

We would like to descend into Haskell ASTs and maintain a
stack of enclosing function names. If we print these stacks
and the encountered names, we get the following:

["f"]"f"
["f"]"x"
["f"]"y"
["f"]"x"
["f"]"o"
["f"]"y"
["o","f"]"o"
["o","f"]"x"
["o","f"]"y"
["o","f"]"x"
["o","f"]"y"

Here comes the generic programming code which does this.

I import the hssource Haskell parser stuff:

module Main where

import ParseWrap
import HsPretty
import Datatypes
import GenericLib

The main function reads the above test file.
and issues a traversal over the parsed test-file.

main = do
         sin  <- readFile "Test.hs"
         pout <- gEnvTraversal [] (parseWrap sin) 
         return ()

The traversal combines a QUERY (see isHsFunBind)
to get new names to be pushed on the stack and
a MONADIC TRANSFORMATION, which in this case 
preserves the term but prints some stuff when
it encounters HsNames (see sayHsName).

--
-- A traversal which
--  maintains an "environment" (here a list of strings)
--  uses a query to update the environment
--  otherwise uses gmap to descend into terms
--
gEnvTraversal:: Term a => [String] -> a -> IO a
gEnvTraversal s x = do
                      y <- mkM (sayHsName s) x
                      gmapM (gEnvTraversal s') y
 where
  s' = maybe s (flip (:) s) ((Nothing `mkQ` isHsFunBind) x)


-- Stop at HsName and print string argument
sayHsName :: [String] -> HsName -> IO HsName
sayHsName s x@(HsIdent y) = do 
                             putStrLn (show s ++ show y)
                             return x
sayHsName s x = return x


-- Stop at HsDecl and return name from HsFunBind term
isHsFunBind :: HsDecl -> Maybe String
isHsFunBind (HsFunBind _ (HsMatch _ (UnQual (HsIdent x)) _ _ _:_)) = Just x
isHsFunBind _ = Nothing


Observations:

- The function is monadic; see use of gmapM.
- You can easily turn the scheme underlying gEnvTraversal
into a parameterised function. (Strafunski offers such 
variations using more ugly types.)
- You could use the environment monad instead of
using an explicit argument of type [String] abbove, but
the point is here that we are not writing much code
anyway thanks to generic programming. So hiding the
fact that we have an environment-like argument is not
attractive.

Ralf

P.S.: I added this example to the gmap distribution (4.0).
http://www.cs.vu.nl/Strafunski/gmap/






> 
> (QM is the monad I'm living in at the moment)
> 
> and then simply apply a gmapM to the HsModule.
> 
> The problem is that "almost" referenced above.  Specifically, I need to do
> something special when I reach an HsFunBind in the source (basically, just
> push the local definitions into an environment held in the QM monad and
> then pop them back out when we leave).
> 
> I have a feeling such a thing should be possible, but I haven't yet
> figured out how.
> 
> My first attempt was essentially to try to do some sort of dispatch
> function using the gmap library.  To simplify, consider:
> 
> newtype Name = Name String deriving (Eq, Ord, Show)
> data Foo = Foo Name [Foo]
>          | Bar Name
>          deriving (Eq, Ord, Show)
> 
> testFoo
>   = Foo (Name "a")
>       [Foo (Name "b")
>         [Bar (Name "c"),
>          Bar (Name "d"),
>          Foo (Name "e")
>            [Bar (Name "f")],
>          Bar (Name "g")],
>        Bar (Name "h")]
> 
> with the obvious instance declarations of Term and Typeable.  Now, we want
> a function which will replace every 'Name s' with 'Name (s++s)' and, when
> we reach a 'Foo s', we want to print ">s" onenter and "<s" on exit.
> 
> we can do the first one with:
> 
> doubleName :: Name -> IO Name
> doubleName (Name s) = return (Name (s++s))
> 
> everywhereM (mkM doubleName) testFoo
> 
> which runs fine and produces the correct results.
> 
> As for the printing, I want something like:
> 
> flagFoo x
>   | x "is a Name"    = doubleName x
>   | x "is a Foo s l" = do
>     putStrLn ('>':s)
>     recurse on s and l
>     putStrLn ('<':s)
>   | otherwier        = gmapM flagFoo x
> 
> or something like that.  obviously I could do this by writing the
> recursion myself, but i'm trying to get around that.  even if i could
> write something like:
> 
> doName = doubleName
> doFoo x@(Foo s l) = do putStrLn ...; something x; putStrLn ...
> doFoo x           = something x
> 
> and then domsehting like:
> 
>   everywhereM (doName .|. doFoo) testFoo
> 
> then that woudl be fine.  I just don't want to have to write out all the
> other cases for doFoo that I don't care about, because for the real
> HsDecl, this is a *lot* of junk.
> 
> Anyone know how to solve this problem?
> 
> --
>  Hal Daume III                                   | hdaume@isi.edu
>  "Arrest this man, he talks in maths."           | www.isi.edu/~hdaume
> 
> _______________________________________________
> Haskell mailing list
> Haskell@haskell.org
> http://www.haskell.org/mailman/listinfo/haskell

-- 
Ralf Laemmel
VU & CWI, Amsterdam, The Netherlands
http://www.cs.vu.nl/~ralf/
http://www.cwi.nl/~ralf/