作为一个例子,我有一辆自行车,前面板和前面板。后轮,两者都有一个Int来代表直径。
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
我明白为什么这不起作用。 position
不会被解释为具有值frontwheel
,而是被解释为position
的(不存在的)字段Bike
。
是否存在(JS)mybike[position] = 26
或(PHP)$mybike->$position = 26
的Haskell模拟?
是否可以以优雅的方式没有任何外部模块?
否则,是否可以使用镜头?
答案 0 :(得分:6)
是的,lens 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'
可用于设置,获取(和遍历)值。如果没有lens
使用的基础Rank2多态性,这只能表达得很尴尬。(Wheel -> Wheel) -> Bike -> Bike
可以做各种垃圾; lens
需要镜头定律,基本上保证镜头实际上像记录存取器一样工作,仅此而已。BTW,对于“修改某些东西”的函数,它在Haskell中是常规的,以便最后修改参数:
replaceWheel :: Setter' Bike Wheel -> Wheel -> Bike -> Bike
replaceWheel position wheel = position .~ wheel
......或者更短,
replaceWheel = (.~)