我的数据类型包含很多字段:
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
。
答案 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
我个人认为运营商有点多。除非你打算在任何地方使用,否则我会坚持使用set
和over
。
我不知道按照你描述的方式解压缩功能的好方法。但是,请仔细看看镜头库的其余部分 - 它非常大,你永远不会知道你会发现什么!