我今天早些时候提出的与this question相关的内容。
我有一个包含大量案例的AST数据类型,它由“注释”类型参数化
data Expr ann def var = Plus a Int Int
| ...
| Times a Int Int
deriving (Data, Typeable, Functor)
我有def和var的具体实例,比如Def
和Var
。
我想要的是自动派生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
我正在与其他人的代码库合作,所以如果我不必在任何地方切换类型参数的顺序,那将是理想的。
答案 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