15

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

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 压缩在一起并将任意数量的函数映射到每个字段上,这听起来像是某种类型类的实例吗?

用例:

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

它对我来说有点像一个应用程序,但我再次不确定如何fmap在这种类型上实现。

4

1 回答 1

19

您无法使用标准功能真正做到这一点。最好的方法是为每个字段设置一个地图功能。令人高兴的是,您可以使用镜头库中的一些模板 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也可以调用.~,over 也可以调用%~。(后者有一个助记符: is mod,或“修改”:P。)这对于刚刚翻转%的运算符也很有用。所以以下两个版本是相等的:&$

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

个人觉得运营商有点多。除非您要在任何地方都使用镜头,否则我会坚持使用setand over

我不知道您描述的压缩功能的好解决方案。但是一定要看看镜头库的其余部分——它非常大,你永远不知道你会找到什么!

于 2013-06-07T02:04:31.920 回答