How to derive multiple Functor instances in Haskell?

420 Views Asked by At

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?

1

There are 1 best solutions below

3
On

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 and a2 are used identically, and they are used once per node. You can factor this.

data ExprNode r a = ExprNode (ExprF r a) a
    -- it's a bifunctor

data ExprF r a
    = Ref r
    | App FunName [ExprNode r a]
    | Lit Value
    -- it's a bifunctor

type Expr r a1 a2 = ExprNode r (a1, a2)

Now all you need is bifunctor. Whether you want to do this depends on what your intended meanings of a1 and a2 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.