I'm defining an AST for expression, and it has three type arguments, like following:
{-# language DeriveFunctor, DeriveFoldable, DeriveTraversable #-}
-- | a general represetation of an expression
-- , with ref info contained in r and two attributes contained in a1, a2
data Expr r a1 a2
= Ref r a1 a2
| App FunName [Expr r a1 a2] a1 a2
| Lit Value a1 a2
deriving (Functor, Foldable, Traversable)
Now using DeriveFunctor
can only help me to define instance Functor (Expr r a1)
, so I can fmap
over a2
, but if I want to fmap
over a1
or r
, I find that it is impossible to use DeriveFunctor
with a newtype
, since the following code doesn't work:
newtype ExprR a1 a2 r = MkExprR { getExpr :: Expr r a1 a2 }
deriving instance Functor (ExprR a1 a2)
If I need only two type arguments, then Bifunctor
may be a good idea, and there indeed is some package which provide DeriveBifunctor
, but what if we need three? do we need DeriveTrifunctor
or DeriveQuadfunctor
etc. ?
And, what if we need more than Functor
? considering Foldable
, Traversable
etc.
Is there any solution about this problem? how does people solve this problem in haskell practice?
Not a direct answer to your question, but an observation. If this is the real type you are working with, it could use some factorization.
a1
anda2
are used identically, and they are used once per node. You can factor this.Now all you need is bifunctor. Whether you want to do this depends on what your intended meanings of
a1
anda2
are, but I suspect you do want to do this. Factoring out uniformity in your data types can clean up the rest of your code significantly: it can tease out hidden opportunities for generalization, reveal standard structures you are relying on, and reduce the amount of boilerplate functions by leaning more on the standard library.