如何使用 Control.Lens 正确格式化函数?

How do I correctly format a function using Control.Lens?

我目前正在通过使用库编写一些简单的函数来学习镜头库。不幸的是,我对生成的编译器错误感到很困惑,所以我很难确定为什么在下面的函数 dmg 中,前两个函数编译正确,但最后一个失败。

import Control.Lens

type Health = Int
type Damage = Int 

data Card = Card {
    _health :: Int,
    _damage :: Int
} deriving (Show,Eq)

health :: Lens' Card Health
health = lens _health (\card h -> card { _health = h })
damage :: Lens' Card Damage
damage = lens _damage (\card d -> card { _damage = d })

cardDead :: Card -> Bool
cardDead c = c^.health <= 0

duel :: (Card,Card) -> (Card,Card)
duel (c,c2) = ((dmg c c2),(dmg c2 c))

事情的实质。

dmg :: Card -> Card -> Card
dmg myCard otherCard = over health ((-) (otherCard^.damage)) myCard --compiles
dmg myCard otherCard = myCard & health %~ ((-) (otherCard^.damage)) --compiles
dmg myCard otherCard = health %~ ((-) (otherCard^.damage)) myCard    --compile error

我的问题分为三个部分。

  1. 为什么第三个dmg函数编译失败

  2. 我如何编写 dmg 来仍然使用 (%~) 运算符,而不使用 (&),并且仍然编译?

  3. 最美的镜头写法是什么dmg?

--

作为参考,这里是您可以在没有镜头的情况下编写 dmg 的一种方法

dmg myCard otherCard = 
    let 
        damageTaken = _damage otherCard
        oldHealth = _health myCard
        newHealth = oldHealth - damageTaken
    in myCard {_health = newHealth}

编辑:作为参考,这是我在理解第 3 行(写错了)时遇到的错误消息。

*Main GHC.Arr Control.Applicative Control.Lens> :l Doom.hs
[1 of 1] Compiling Main             ( Doom.hs, interpreted )

Doom.hs:26:24:
    Couldn't match expected type `Card' with actual type `Card -> Card'
    In the expression: health %~ ((-) (otherCard ^. damage)) myCard
    In an equation for `dmg':
        dmg myCard otherCard = health %~ ((-) (otherCard ^. damage)) myCard

Doom.hs:26:51:
    Couldn't match type `Health -> Health' with `Int'
    Expected type: Getting (Health -> Health) Card (Health -> Health)
      Actual type: (Damage -> Const (Health -> Health) Damage)
                   -> Card -> Const (Health -> Health) Card
    In the second argument of `(^.)', namely `damage'
    In the first argument of `(-)', namely `(otherCard ^. damage)'

Doom.hs:26:60:
    Couldn't match expected type `Health -> Health'
                with actual type `Card'
    In the second argument of `(-)', namely `myCard'
    In the second argument of `(%~)', namely
      `((-) (otherCard ^. damage)) myCard'
Failed, modules loaded: none.
Prelude GHC.Arr Control.Applicative Control.Lens>
  1. 因为解析规则。您的代码格式为

    abc = def %~ ghi jkl
    

    函数应用程序比任何中缀运算符绑定得更紧密,因此它被解析为

    abc = def %~ (ghi jkl)
    

    即其他语言会写什么 def %~ ghi(jkl).

  2. 您需要 (def %~ ghi) jkl。这通常通过 Haskell 中的 $ 来完成,即

    dmg myCard otherCard = health %~ ((-) (otherCard^.damage)) $ myCard
    
  3. 首先我会去掉不必要的括号。运算符部分通常比应用中缀的表达式更好,即

    dmg myCard otherCard = health %~ ((otherCard^.damage) -) $ myCard
    

    ...由于

    ,可以省略内部括号
    Prelude> :info Control.Lens.^.
    ...
    infixl 8 Control.Lens.Getter.^.
    Prelude> :i -
    ...
    infixl 6 -
    

    即无论如何,^.- 结合得更紧密,给

    dmg myCard otherCard = health %~ (otherCard^.damage -) $ myCard
    

    接下来我会尝试减少 η。如果交换参数,这将很容易,这可能是更 Haskell 惯用的参数顺序:

    dmg otherCard myCard = health %~ (otherCard^.damage -) $ myCard
    dmg otherCard = health %~ (otherCard^.damage -)
    

    这可能是最优雅的解决方案。

也就是说,假设您的代码实际上是正确的。我不知道 dmg 应该做什么,但也许更常见的情况是你想从你的伤害中减去另一张卡的伤害。 IE。基本上不是 (otherCard^.damage -),而是 (- otherCard^.damage),除非它被解析为一元减号,因此需要写成 subtract (otherCard^.damage)。 Lens 有专门的加减运算符,给你

dmg otherCard = health -~ otherCard^.damage