Haskell是否提供了将函数映射到数据成员的方法?

时间:2017-04-05 20:42:54

标签: haskell functional-programming records

我是一名Haskell新手,我经常发现自己必须使用模式匹配来分解数据,只是将一个函数应用于其成员之一,然后重新组装它。

说我有:

data Car = Car { gas :: Int, licensePlate :: String }

我希望它在驱动时将其汽油减半并加油,我正在做:

mapGas:: (Int -> Int) -> Car -> Car
mapGas f (Car aGas aLicensePlate) = Car (f aGas) aLicensePlate

drive:: Car -> Car
drive = mapGas (flip div 2)

refuel:: Int -> Car -> Car
refuel = mapGas . (+)

有没有办法在不必定义辅助函数mapGas的情况下做到这一点?因为当它由许多字段组成时,必须为每个数据成员编写一个map函数会变得相当麻烦。我知道可以为具有访问者的成员之一分配值:

runOutOfFuel:: Car -> Car
runOutOfFuel aCar = aCar { gas = 0 }

是否也可以使用访问器映射函数?如果是这样,怎么样?

2 个答案:

答案 0 :(得分:13)

仅使用核心库?不,但是使用广泛使用的lens包,是的。以下是您的情况:

{-# LANGUAGE TemplateHaskell #-}

import Control.Lens.TH
import Control.Lens

data Car = Car { _gas :: Int, _licensePlate :: String }

makeLenses ''Car

现在,您可以轻松获取/设置/修改嵌套在数据结构中的字段。

runOutOfFuel:: Car -> Car
runOutOfFuel = gas .~ 0

drive:: Car -> Car
drive = gas %~ (`div` 2)

refuel:: Int -> Car -> Car
refuel c = gas +~ c

这里的神奇之处在于,makeLenses ''Car生成的gaslicensePlate函数与mapGas相似(但更强大)(实际上,mapGas = (gas %~) )。 lens入门非常令人生畏,但我建议您只阅读examples部分。

答案 1 :(得分:4)

没有语言功能这样做,但是,与Haskell中的许多内容一样,核心语言足够强大,可以以简单而优雅的方式实现。

您正在寻找的解决方案是一种称为镜头的值。镜头完全符合您的要求:它允许您采用任何类型的抽象数据并在其中的一部分上应用函数,从而获得包含修改部分的整个数据值。

我非常喜欢here的镜头介绍。要使用包含的示例,您需要lens包。 (Or this one if you're using Stack