Haskell:基于变量替换记录中的字段

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

举个例子,我有一辆自行车,前后轮都有一个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 
我确实明白为什么那不起作用<代码>位置不解释为具有值
前轮
,而是解释为
自行车
的(不存在)字段
位置

是否存在(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'
    可用于设置、获取(和遍历)值。如果没有
    lens
    使用的基础Rank2多态性,这只能笨拙地表达出来
  • 更简洁。上述类型有很多冗余;lens为您提供了这些访问器的简短同义词
  • 更安全。一个功能
    (轮子->轮子)->自行车->自行车
    可以做各种垃圾<代码>镜头要求镜头法则,基本上保证镜头实际上像一个记录存取器一样工作,仅此而已
  • 快。镜头库中的组合器是在考虑性能的情况下编写的(即支持流融合的内联,在状态monad中省略复制等)

顺便说一句,对于“修改某些内容”的函数,Haskell的惯例是将要修改的参数放在最后:

replaceWheel :: Setter' Bike Wheel -> Wheel -> Bike -> Bike
replaceWheel position wheel = position .~ wheel
。。。或者,甚至更短

replaceWheel = (.~)

这就是镜头的全部内容。谢谢你的全面解释!运行
cabal install lens
并将
{-#LANGUAGE TemplateHaskell}
添加到我的文件顶部后,效果非常好。您还回答了我的另一个问题:如何使用函数转换字段。原来
(~)
适合我的需要!