Haskell:基于变量替换记录中的字段
举个例子,我有一辆自行车,前后轮都有一个Int来表示直径Haskell:基于变量替换记录中的字段,haskell,types,Haskell,Types,举个例子,我有一辆自行车,前后轮都有一个Int来表示直径 type Wheel = Int data Bike = Bike { frontwheel :: Wheel, rearwheel :: Wheel } deriving (Show) mybike = Bike 24 26 现在我想换一个轮子,因为我不喜欢它们的尺寸不同: replaceFrontWheel :: Bike -> Wheel -> Bike replaceFrontWheel bike whee
type Wheel = Int
data Bike = Bike { frontwheel :: Wheel, rearwheel :: Wheel }
deriving (Show)
mybike = Bike 24 26
现在我想换一个轮子,因为我不喜欢它们的尺寸不同:
replaceFrontWheel :: Bike -> Wheel -> Bike
replaceFrontWheel bike wheel = bike { frontwheel = wheel }
repairedbike = replaceFrontWheel mybike 26
真管用
但是如果我想要一个可以替换前轮或后轮的功能呢?毕竟,两个控制盘都是Wheel(Int)类型,所以为什么不使用一个将字段作为参数的函数来执行此操作:
replaceWheel bike position wheel = bike { position = wheel }
repairedbike = replaceWheel mybike frontwheel 26
我确实明白为什么那不起作用<代码>位置不解释为具有值前轮
,而是解释为自行车
的(不存在)字段位置
是否存在(JS)mybike[position]=26
或(PHP)$mybike->$position=26
的Haskell类似物
是否可以在没有任何外部模块的情况下以优雅的方式实现
否则,是否可以使用镜头?是的,es正是您所需要的
import Control.Lens
import Control.Lens.TH
data Bike = Bike { _frontwheel, _rearwheel :: Wheel }
deriving (Show)
makeLenses ''Bike
replaceWheel :: Bike -> Lens' Bike Wheel -> Wheel -> Bike
replaceWheel bike position wheel = bike & position .~ wheel
如您所愿使用:
repairedbike = replaceWheel mybike frontwheel 26
您可以稍微弱化签名:
replaceWheel :: Bike -> Setter' Bike Wheel -> Wheel -> Bike
这基本上只是一种花哨的说法
replaceWheel :: Bike
-> ((Wheel->Identity Wheel) -> (Bike->Identity Bike))
-> Wheel
-> Bike
因为Identity
只是一个类型级同构,所以您不妨省略它,这最终会导致
replaceWheel :: Bike -> ((Wheel->Wheel) -> Bike->Bike) -> Wheel -> Bike
replaceWheel bike position wheel = bike & position (const wheel)
-- = position (const wheel) bike
可以这样说:
data Bike = Bike { _frontwheel, _rearwheel :: Wheel } -- no lenses
frontWheel :: (Wheel -> Wheel) -> Bike -> Bike
frontWheel f (Bike fw rw) = Bike (f fw) rw
repairedbike = replaceWheel mybike frontwheel 26
所以,严格地说,你不需要任何图书馆
更倾向于使用合适的镜头而不是这种特殊近似的原因包括:
- 更一般。
可用于设置、获取(和遍历)值。如果没有Lens'
使用的基础Rank2多态性,这只能笨拙地表达出来lens
- 更简洁。上述类型有很多冗余;lens为您提供了这些访问器的简短同义词
- 更安全。一个功能
可以做各种垃圾<代码>镜头要求镜头法则,基本上保证镜头实际上像一个记录存取器一样工作,仅此而已(轮子->轮子)->自行车->自行车
- 快。镜头库中的组合器是在考虑性能的情况下编写的(即支持流融合的内联,在状态monad中省略复制等)
顺便说一句,对于“修改某些内容”的函数,Haskell的惯例是将要修改的参数放在最后:
replaceWheel :: Setter' Bike Wheel -> Wheel -> Bike -> Bike
replaceWheel position wheel = position .~ wheel
。。。或者,甚至更短
replaceWheel = (.~)
这就是镜头的全部内容。谢谢你的全面解释!运行
cabal install lens
并将{-#LANGUAGE TemplateHaskell}
添加到我的文件顶部后,效果非常好。您还回答了我的另一个问题:如何使用函数转换字段。原来(~)
适合我的需要!