Getting extensions with multiparameter types
I have a constructor of type Ast, parameterized with an identifier type. Using DeriveFunctor, DeriveFoldable and DeriveTraversable extensions it is possible to automatically create corresponding instances.
Now I find it helpful to introduce more type parameters, but unfortunately the above method does not scale. Ideally, I would like to be able to wrap my Astra type in select types, which would allow me to fmap to the appropriate type parameters. Is there a way to achieve a similar effect without having to define instances yourself?
edit:
Here's a small example of what the original Ast looked like:
Ast id = Ast (FuncDef id)
deriving (Show, Functor, Foldable, Traversable)
FuncDef id = FuncDef id [Fparam id] (Block id)
deriving (Show, Functor, Foldable, Traversable)
Block id = Block [Stmt id]
deriving (Show, Functor, Foldable, Traversable)
..
source to share
After walking all day, I came to the following conclusions:
The ast presented in the question turned out to be not very flexible. In later stages, I want to annotate the various nodes in a way that cannot be expressed simply by parameterizing the original Ast.
So, I changed Ast to act as a basis for writing new types:
Ast funcdef = Ast funcdef
deriving (Show)
Header id fpartype = Header id (Maybe Type) [fpartype]
deriving (Show)
FuncDef header block = FuncDef header block
deriving (Show)
Block stmt = Block [stmt]
deriving (Show)
Stmt lvalue expr funccall =
StmtAssign lvalue expr |
StmtFuncCall funccall |
..
Expr expr intConst lvalue funccall =
ExprIntConst intConst |
ExprLvalue lvalue |
ExprFuncCall funccall |
expr :+ expr |
expr :- expr |
..
Now I can simply define a chain of new types for each stage of compilation. Ast in the renamer stage can be parameterized around the type of the identifier:
newtype RAst id = RAst { ast :: Ast (RFuncDef id) }
newtype RHeader id = RHeader { header :: Header id (RFparType id) }
newtype RFuncDef id = RFuncDef {
funcDef :: FuncDef (RHeader id) (RBlock id)
}
..
newtype RExpr id = RExpr {
expr :: Expr (RExpr id) RIntConst (RLvalue id) (RFuncCall id)
}
During the type checking phase of Ast, various internal types used in the nodes can be parameterized.
This parameterization allows you to build Asts with Maybe
wrapped parameters in the middle of each step.
If everything is ok, we can use fmap
to delete Maybe
and prepare the tree for the next stage. There are other uses Functor, Foldable
and Traversable
therefore must have.
At this point I decided that what I want is most likely not possible without metaprogramming, so I was looking for a haskell pattern solution. Of course, there is a genifunctors library that implements common
fmap, foldMap
and traverse
. With these, it's a simple matter of writing multiple newtype skins to create different instances of the required typeclasses around the corresponding parameters:
fmapGAst = $(genFmap Ast)
foldMapGAst = $(genFoldMap Ast)
traverseGast = $(genTraverse Ast)
newtype OverT1 t2 t3 t1 = OverT1 {unwrapT1 :: Ast t1 t2 t3 }
newtype OverT2 t1 t3 t2 = OverT2 {unwrapT2 :: Ast t1 t2 t3 }
newtype OverT3 t1 t2 t3 = OverT3 {unwrapT3 :: Ast t1 t2 t3 }
instance Functor (OverT1 a b) where
fmap f w = OverT1 $ fmapGAst f id id $ unwrapT1 w
instance Functor (OverT2 a b) where
fmap f w = OverT2 $ fmapGAst id f id $ unwrapT2 w
instance Functor (OverT3 a b) where
fmap f w = OverT3 $ fmapGAst id id f $ unwrapT3 w
..
source to share