将镜头传递给函数
Pass a lens into a funciton
如何正确地将一个镜头传递给一个带有状态的函数?让我们考虑下一个代码:
{-# 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
的情况下实现相同的算法?
只需使用类型签名!
这是怎么回事:如果您不提供签名,GHC 将只能推断出 Rank-1 类型†。在此示例中,您将 defender.troops
用作 getter‡;因此,编译器为 defender
推断出 getter 类型。这是错误消息中的丑陋之处,其中包含 Const
。
但是,您还想将其用作 setter。这只有在 defender
是多态的情况下才有可能(因此您可以使用 Identity
仿函数而不是 Const
),并且对于多态的参数,您需要 Rank-2 多态性。
虽然您真的不需要担心这种类别理论的魔力,因为镜头库提供了易于使用的同义词。只需写签名 ,就像你应该 总是 那样,
offend :: Traversal' Game Army -> Traversal' Game Army -> State Game BattleResult
你得到了正确的多态参数。啊,当然你需要 -XRankNTypes
扩展。 -XFlexibleContexts
实际上不是必需的(尽管它完全无害,没有理由不使用它)。
†如果你问我的话,Hindley-Milner 无论如何都是一个奇迹,但它之所以有效,是因为有一个定义明确的 most任何表达式的一般可能 签名。不过,这只是 Rank-1 代码的情况:对于 Rank-N,您总是可以加入另一层通用量化。编译器不知道什么时候结束!
‡其实是一个Getting
,也就是遍历-getter。 Getter
和 Getting
之间的区别在于后者可以是部分的(这是必要的,因为您使用 ix
;编译器无法证明实际上在索引 1 处有一个元素军队列表)。
如何正确地将一个镜头传递给一个带有状态的函数?让我们考虑下一个代码:
{-# 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
的情况下实现相同的算法?
只需使用类型签名!
这是怎么回事:如果您不提供签名,GHC 将只能推断出 Rank-1 类型†。在此示例中,您将 defender.troops
用作 getter‡;因此,编译器为 defender
推断出 getter 类型。这是错误消息中的丑陋之处,其中包含 Const
。
但是,您还想将其用作 setter。这只有在 defender
是多态的情况下才有可能(因此您可以使用 Identity
仿函数而不是 Const
),并且对于多态的参数,您需要 Rank-2 多态性。
虽然您真的不需要担心这种类别理论的魔力,因为镜头库提供了易于使用的同义词。只需写签名 ,就像你应该 总是 那样,
offend :: Traversal' Game Army -> Traversal' Game Army -> State Game BattleResult
你得到了正确的多态参数。啊,当然你需要 -XRankNTypes
扩展。 -XFlexibleContexts
实际上不是必需的(尽管它完全无害,没有理由不使用它)。
†如果你问我的话,Hindley-Milner 无论如何都是一个奇迹,但它之所以有效,是因为有一个定义明确的 most任何表达式的一般可能 签名。不过,这只是 Rank-1 代码的情况:对于 Rank-N,您总是可以加入另一层通用量化。编译器不知道什么时候结束!
‡其实是一个Getting
,也就是遍历-getter。 Getter
和 Getting
之间的区别在于后者可以是部分的(这是必要的,因为您使用 ix
;编译器无法证明实际上在索引 1 处有一个元素军队列表)。