Haskell 将镜头传递到函数中

Haskell 将镜头传递到函数中,haskell,state-monad,haskell-lens,Haskell,State Monad,Haskell Lens,如何正确地将镜头传递到具有状态的函数中?让我们考虑下一个代码: {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE FlexibleContexts #-} import Control.Lens import Control.Monad.State data Game = Game { _armies :: [Army] } deriving (Show) data Army = Army { _troops ::

如何正确地将镜头传递到具有状态的函数中?让我们考虑下一个代码:

{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE FlexibleContexts #-}

import Control.Lens
import Control.Monad.State

data Game = Game { _armies :: [Army]
                 } deriving (Show)

data Army = Army { _troops :: Int
                 } deriving (Show)

makeLenses ''Game
makeLenses ''Army

data BattleResult = Win | Defeat deriving (Show)

offend offender defender = do
  Just ot <- preuse $ offender.troops
  Just dt <- preuse $ defender.troops
  defender.troops.=0 -- doesn't work
  let eval a b
        | a >= b    = return Win
        | otherwise = return Defeat
  eval ot dt

game :: State Game ()
game = do
    armies %= (:) (Army 100)
    armies %= (:) (Army 200)
    q <- offend (armies.ix 0) (armies.ix 1)
    return ()
如果将该行替换为类似
arms.ix 0.forces.=0
的内容,则通常会编译代码。是否有一些标准的工具来解决这个问题?如果不使用FlexibleContexts,是否可以实现相同的算法?

只需使用类型签名

这里发生了什么:如果您不提供签名,GHC将只能推断排名1的类型†。在本例中,您将使用
defender.forces
作为getter&ddagger;;因此,编译器推断出
defender
的getter类型。这是错误消息中丑陋的东西,其中包含
Const

但是,您还希望将其用作setter。只有当
defender
是多态的(因此您可以使用
Identity
函子而不是
Const
)时,才可能出现这种情况,并且对于多态的参数,您需要秩2多态性

不过,你真的不需要担心这种类别理论的魔力,因为lens库提供了易于使用的同义词。只要写下签名就可以了,不管怎样,你都应该这样做

得到正确的多态参数。啊,当然你需要
-XRankNTypes
扩展<代码>-XFlexibleContexts实际上是不需要的(虽然它完全无害,但没有理由不使用它)


†如果你问我,Hindley Milner无论如何都是一个奇迹,但它只起作用,因为任何表达式都有一个定义良好的最通用的可能签名。然而,这只是秩1代码的情况:对于秩N,您总是可以加入另一层通用量化。编译器无法知道何时结束此操作


&达格尔;实际上是一个
获取
,它是一个遍历获取程序。
Getter
Getting
之间的区别在于后者可以是部分的(这是必要的,因为您使用的是
ix
;编译器无法证明列表中的索引1处实际上有一个元素)。

还有一个问题。当我尝试在
返回Win
之前的嵌套
do
块中放置行
defender.forces.=0
时,编译器抛出一个错误并建议再次使用
FlexibleContexts
。没有这个扩展怎么办?还有什么比使用前更好的吗?我在合适的地方用吗?真的吗?嵌套的
do
块在这里不会有太大区别。。。但是,如果它让编译器感到高兴,见鬼,只需打开
-XFlexibleContexts
,正如我所说的,我认为没有理由不这样做<代码>预使用在这里很好,这也是我要使用的。那
让我们评估…
有点奇怪。为什么不跳过这一点,只写
如果a>=b,那么返回Win,否则返回fail
?@dfeuer,如果我需要更多的case(例如>,
case()of |…
是经典的。GHC
MultiWayIf
提供了更好的语法。
Lens.hs:21:3:
    Couldn't match type ‘Const (Data.Monoid.First Int) s’
                   with ‘Identity s’
    Expected type: (Army -> Const (Data.Monoid.First Int) Army)
                   -> s -> Identity s
      Actual type: (Army -> Const (Data.Monoid.First Int) Army)
                   -> s -> Const (Data.Monoid.First Int) s
    Relevant bindings include
      defender :: (Army -> Const (Data.Monoid.First Int) Army)
                  -> s -> Const (Data.Monoid.First Int) s
        (bound at Lens.hs:18:17)
      offender :: (Army -> Const (Data.Monoid.First Int) Army)
                  -> s -> Const (Data.Monoid.First Int) s
        (bound at Lens.hs:18:8)
      offend :: ((Army -> Const (Data.Monoid.First Int) Army)
                 -> s -> Const (Data.Monoid.First Int) s)
                -> ((Army -> Const (Data.Monoid.First Int) Army)
                    -> s -> Const (Data.Monoid.First Int) s)
                -> m BattleResult
        (bound at Lens.hs:18:1)
    In the first argument of ‘(.)’, namely ‘defender’
    In the first argument of ‘(.=)’, namely ‘defender . troops’

Lens.hs:21:12:
    Couldn't match type ‘Identity Integer’
                   with ‘Const (Data.Monoid.First Int) Int’
    Expected type: (Int -> Identity Integer)
                   -> Army -> Const (Data.Monoid.First Int) Army
      Actual type: (Int -> Const (Data.Monoid.First Int) Int)
                   -> Army -> Const (Data.Monoid.First Int) Army
    In the second argument of ‘(.)’, namely ‘troops’
    In the first argument of ‘(.=)’, namely ‘defender . troops’
offend :: Traversal' Game Army -> Traversal' Game Army -> State Game BattleResult