我正在为表达式定义一个AST,它有三个类型参数,如下所示:
{-# 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)
现在使用DeriveFunctor只能帮我定义实例Functor(Expr r a1),所以我可以fmap over a2,但如果我想通过a1或r进行fmap,我发现不可能将DeriveFunctor与newtype一起使用,因为以下代码不起作用:
newtype ExprR a1 a2 r = MkExprR { getExpr :: Expr r a1 a2 }
deriving instance Functor (ExprR a1 a2)
如果我只需要两个类型的参数,那么Bifunctor可能是一个好主意,确实有一些包提供了DeriveBifunctor,但如果我们需要三个呢?我们需要DeriveTrifunctor或DeriveQuadfunctor等吗?
而且,如果我们需要的不仅仅是Functor呢?考虑可折叠,可穿越等
这个问题有什么解决方案吗?人们如何在haskell实践中解决这个问题?
最佳答案 不是你问题的直接答案,而是观察.如果这是您正在使用的真实类型,则可以使用一些因子分解. a1和a2的使用方式相同,每个节点使用一次.你可以考虑这一点.
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)
现在你需要的只是bifunctor.你是否想要这样做取决于a1和a2的意图,但我怀疑你确实想要这样做.考虑到数据类型的一致性可以显着地清理其余的代码:它可以挖掘隐藏的泛化机会,揭示您所依赖的标准结构,并通过更多地依赖标准库来减少样板函数的数量.