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

时间:2017-06-02 11:20:55

标签: haskell 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的最美丽,惯用的镜头方式是什么?

  4. -

    作为参考,这里有一种你可以写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 个答案:

答案 0 :(得分:3)

  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
    

    ...由于

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

    即。 ^.绑定比-更紧密,给予<​​/ p>

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

    接下来我会尝试η-reduction。如果参数被交换,这很容易,这可能是更多Haskell-idiomatic参数顺序:

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

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

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

    dmg otherCard = health -~ otherCard^.damage
    
相关问题