Haskell:根据变量替换记录中的字段

okd*_*wit 2 haskell types

作为一个例子,我有一个带前后轮的自行车,它们都只有一个Int来代表直径.

type Wheel = Int
data Bike = Bike { frontwheel :: Wheel, rearwheel :: Wheel }
    deriving (Show)

mybike = Bike 24 26
Run Code Online (Sandbox Code Playgroud)

现在我想更换一个轮子,因为我不喜欢它们的尺寸不同:

replaceFrontWheel :: Bike -> Wheel -> Bike
replaceFrontWheel bike wheel = bike { frontwheel = wheel }

repairedbike = replaceFrontWheel mybike 26 
Run Code Online (Sandbox Code Playgroud)

这样可行!

但是,如果我想要一个可以替换前轮后轮的功能呢?毕竟两个轮子都是Wheel(Int)类型,所以为什么不使用单个函数来实现它,该函数也将该字段作为参数:

replaceWheel bike position wheel = bike { position = wheel }

repairedbike = replaceWheel mybike frontwheel 26 
Run Code Online (Sandbox Code Playgroud)

我明白为什么这不起作用.position不被解释为具有值frontwheel,但作为(不存在)字段positionBike.

是否有(JS)mybike[position] = 26或(PHP)的Haskell模拟$mybike->$position = 26

没有任何外部模块,是否可以以优雅的方式进行?

否则,是否可以使用镜头?

lef*_*out 6

是的,镜头正是您所需要的.

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
Run Code Online (Sandbox Code Playgroud)

要像你想要的那样使用:

repairedbike = replaceWheel mybike frontwheel 26
Run Code Online (Sandbox Code Playgroud)

你可以稍微削弱签名:

replaceWheel :: Bike -> Setter' Bike Wheel -> Wheel -> Bike
Run Code Online (Sandbox Code Playgroud)

这基本上只是一种奇特的说法

replaceWheel :: Bike
             -> ((Wheel->Identity Wheel) -> (Bike->Identity Bike))
             -> Wheel
             -> Bike
Run Code Online (Sandbox Code Playgroud)

因为Identity它只是一个类型级别的同构,所以你最好省略它,最终会让你失望

replaceWheel :: Bike -> ((Wheel->Wheel) -> Bike->Bike) -> Wheel -> Bike
replaceWheel bike position wheel = bike & position (const wheel)
                              -- = position (const wheel) bike
Run Code Online (Sandbox Code Playgroud)

这可以这样称呼:

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
Run Code Online (Sandbox Code Playgroud)

所以,你确实没有严格要求任何图书馆!

使用合适的镜头而不是这种临时近似的原因包括:

  • 更一般.A Lens'可以用于设置,获取(和遍历)值.这只能在没有使用的基础Rank2多态性的情况下表达得很尴尬lens.
  • 更简洁.上述类型有很多冗余; 镜头为您提供这些访问者的简短同义词.
  • 更安全.一个功能(Wheel -> Wheel) -> Bike -> Bike可以做各种垃圾; lens需要镜头规律,基本上保证镜头实际上像记录存取器一样工作,仅此而已.
  • 快速.镜头库中的组合器在编写时考虑了性能(即支持流融合的内联,在状态monad中省略复制等).

顺便说一句,对于"修改某些东西"的函数,它在Haskell中是常规的,以便最后修改参数:

replaceWheel :: Setter' Bike Wheel -> Wheel -> Bike -> Bike
replaceWheel position wheel = position .~ wheel
Run Code Online (Sandbox Code Playgroud)

......或者更短,

replaceWheel = (.~)
Run Code Online (Sandbox Code Playgroud)