举个例子,我有一辆自行车,有前轮和后轮,它们都有一个 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
,而是被解释为 Bike
的(不存在的)字段 position
>.
是否有 (JS) mybike[position] = 26
或 (PHP) $mybike->$position = 26
的 Haskell 模拟?
是否可以在没有任何外部模块的情况下以优雅的方式实现?
不然的话,用镜头有没有可能?
最佳答案
是的,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 多态性,这只能笨拙地表达。 - 更简洁。上面的类型有很多冗余; lens 为您提供了这些访问器的简短同义词。
- 更安全。函数
(Wheel -> Wheel) -> Bike -> Bike
可以处理各种垃圾;lens
需要 lens 法则,该法则基本上保证了 lens 实际上像唱片访问器(accessor)一样工作,仅此而已。 - 快。 lens 库中的组合器在编写时考虑了性能(即支持流融合的内联,省略状态 monad 中的复制等)。
顺便说一句,对于“修改某些东西”的函数,在 Haskell 中通常将要修改的参数放在最后:
replaceWheel :: Setter' Bike Wheel -> Wheel -> Bike -> Bike
replaceWheel position wheel = position .~ wheel
...或者,更短,
replaceWheel = (.~)
关于Haskell:根据变量替换记录中的字段,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39551523/