如何将镜头正确传递到具有状态的功能?让我们考虑下一个代码:
{-# 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 ()
标记的行会导致下一个错误:
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’
如果用armies.ix 0.troops.=0
之类的内容替换该行,则通常会编译代码。是否有一些标准工具来解决问题?并且可以在不使用FlexibleContexts
的情况下实现相同的算法吗?
答案 0 :(得分:2)
只需使用类型签名!
此处发生了什么:如果您没有提供签名,GHC将只能推断Rank-1类型†。在此示例中,您使用def perm(a):
if len(a) <= 1:
yield a
else:
for i in xrange(len(a)):
for p in perm(a[:i]+a[i+1:]):
yield [a[i]]+p
a = [1, 2, 3]
for p in perm(a):
print(p)
[1, 2, 3]
[1, 3, 2]
[2, 1, 3]
[2, 3, 1]
[3, 1, 2]
[3, 2, 1]
作为getter ‡;因此,编译器推断出defender.troops
的getter类型。这是包含defender
的错误消息中的丑陋内容。
但是,您还希望将其用作设置器。这只有Const
是多态的(因此你可以使用defender
仿函数而不是Identity
),并且对于多态的参数,你需要Rank-2多态性。
你并不需要担心这种类别理论的魔力,因为镜头库提供了易于使用的同义词。只需写下签名就像始终一样,
Const
你得到了正确的多态参数。啊,当然你需要offend :: Traversal' Game Army -> Traversal' Game Army -> State Game BattleResult
扩展名。 -XRankNTypes
实际上并不是必需的(但它完全无害,没有理由不使用它)。
† 如果你问我的话,Hindley-Milner无论如何都是一个奇迹,但它才有用,因为那里有一个明确定义的最普遍的任何表达的签名。这只是Rank-1代码的情况:使用Rank-N,你总是可以投入另一层通用量化。编译器无法知道何时结束此操作!
‡ 实际上是一个-XFlexibleContexts
,它是一个遍历获取者。 Getting
和Getter
之间的区别在于后者可以是部分的(这是必要的,因为您使用Getting
;编译器无法证明索引1处实际存在元素在军队清单中。)