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)

..

      

+3


source to share


1 answer


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

..

      

+1


source







All Articles