将函数映射到代数数据类型字段的更简洁方法?

时间:2013-06-07 01:25:58

标签: haskell algebraic-data-types

我的数据类型包含很多字段:

data ManyFields a b c d .. = MF { f1 :: a, f2 :: b, f3 :: c .. }

问题一

如何将函数映射到每个字段,同时避免为每个字段实现map函数。 例如,这看起来非常繁琐且非惯用:

-- | Note I am explicitly constructing ManyField after mapping a function onto the field
-- | This looks bad
mapf1 :: (a -> a1) -> ManyFields a b c ..  -> ManyFields a1 b c ..
mapf1 g mf = MF (g . f1 $ mf) (f2 mf) ..

-- | Repeat for each field
mapf2 :: (b -> b1) -> ManyFields a b c .. -> ManyFields a b1 c ...

我认为抽象出constructor . (mapfunction f)模式的某种高阶函数会削弱样板,但是有更好的解决方案吗?

问题二

如果我想将多个ManyFields压缩在一起并将任意arity的函数映射到每个字段上,这听起来像是某个类型类的实例吗?

用例:

(==) `mapFunction` mf1 `pairWiseZipField` mf2 

它有点像我的应用,但我不知道如何在这种类型上实现fmap

1 个答案:

答案 0 :(得分:19)

使用标准功能无法真正做到这一点。最好的方法是为每个字段设置一个map函数。令人高兴的是,您可以使用lens库中的一些模板haskell自动生成这些内容。它看起来像这样:

data ManyFields a b c d = MF { _f1 :: a, _f2 :: b, _f3 :: c, _f4 :: d }

makeLenses ''ManyFields

这会为ManyFields的每个字段生成镜头。镜头是一个简单的结构,允许您访问更改那里的值 - 变化甚至可以是多态的,就像地图一样!请注意每个字段的前缀是下划线:镜头的名称与字段相同,减去下划线。

您现在可以访问以下值:

> foo = MF 'a' "b" 3 False
> foo^.f1
'a'

您可以使用set运算符设置值。与镜头一起使用时,它会创建一个setter功能:

> :t set f1
set f1 :: a' -> ManyFields a b c d -> ManyFields a' b c d

要实际使用它,你可以这样做:

> set f1 () foo
MF () "b" 3 False

由于你有一个getter和一个setter,所以编写一个map函数非常简单。令人高兴的是,我们甚至不必这样做:库提供了一个名为over的函数:

> :t over f1 
over f1 :: (a -> a') -> ManyFields a b c d -> ManyFields a' b c d

如果您更喜欢中缀运算符,set也可以称为.~,可以将其称为%~。 (后者有一个助记符:%是mod,或“修改”:P。)这对&运算符只有$翻转也很有用。所以以下两个版本是相同的:

> over f1 ord foo
MF 97 "b" 3 False
> foo & f1 %~ ord
MF 97 "b" 3 False

我个人认为运营商有点多。除非你打算在任何地方使用,否则我会坚持使用setover

我不知道按照你描述的方式解压缩功能的好方法。但是,请仔细看看镜头库的其余部分 - 它非常大,你永远不会知道你会发现什么!