派生函子实例,而不是最后一个类型参数

时间:2014-12-28 07:02:48

标签: haskell ghc scrap-your-boilerplate ghc-generics

我今天早些时候提出的与this question相关的内容。

我有一个包含大量案例的AST数据类型,它由“注释”类型参数化

data Expr ann def var = Plus a Int Int
    | ...
    | Times a Int Int
    deriving (Data, Typeable, Functor)

我有def和var的具体实例,比如DefVar

我想要的是自动派生fmap,它在第一个参数上作为仿函数运行。我想得到一个看起来像这样的函数:

fmap :: (a -> b) -> (Expr a Def Var) -> (Expr b Def Var)

当我使用普通fmap时,我得到一条编译器消息,指示fmap正在尝试将其函数应用于最后一个类型参数,而不是第一个。

有没有办法可以如上所述派生出这个函数,而无需编写一堆样板文件?我试过这样做:

newtype Expr' a = E (Expr a Def Var)
    deriving (Data, Typeable, Functor)

但是我收到以下错误:

  Constructor `E' must use the type variable only as the last argument of a data type

我正在与其他人的代码库合作,所以如果我不必在任何地方切换类型参数的顺序,那将是理想的。

2 个答案:

答案 0 :(得分:3)

简短的回答是,这是不可能的,因为Functor要求更改类型变量位于最后位置。只有类型* -> *的类型构造函数可以包含Functor个实例,而您的Expr不具有此类实例。

你真的需要Functor个实例吗?如果你只是想避免编写类似fmap函数的样板,那么像SYB这样的东西是一个更好的解决方案(但实际上样板并不是那么糟糕,你只能写一次)。

如果由于某些其他原因需要Functor(可能您希望在某个具有Functor约束的函数中使用此数据结构),您必须选择是否需要该实例或当前顺序中的类型变量。

答案 1 :(得分:2)

您可以利用类型同义词来最小化对原始代码的更改:

data Expr' def var ann = Plus a Int Int   -- change this to Expr', correct order
    | ...
    | Something (Expr ann def var)        -- leave this as it is, with the original order
    deriving (Data, Typeable, Functor)

type Expr ann def var = Expr' def var ann

其余代码可以继续使用Expr,不变。唯一的例外是类实例,例如Functor,您注意到这些实例需要参数中的某个顺序。希望Functor是你唯一需要的课程。

自动派生的fmap函数具有类型

fmap :: (a -> b) -> Expr' def var a -> Expr' def var b

可以写成

fmap :: (a -> b) -> Expr a def var -> Expr b def var