2016-03-05 28 views
2

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

{-# 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

'让eval ...'有点奇怪。为什么不跳过那个,只是写'如果a> = b然后返回赢,否则返回失败'? – dfeuer

+0

@dfeuer,如果我需要更多的案例(例如>,<和==),我必须使用这个'let'表达式。你知道还有其他方法在'do'块中执行守卫吗? – Leonid

+1

'_的情况() ......是经典的。 GHC'MultiWayIf'为它提供了更好的语法。 – dfeuer

回答

2

只需使用类型签名!

这里发生了什么:如果您不提供签名,GHC将只能推断Rank-1类型&dagger;。在本例中,您使用defender.troops作为吸气剂&ddagger;;编译器因此推断defender的吸气剂类型。这是错误消息中的Const中的丑陋之处。

但是,您也想将其用作setter。这是唯一可能的,如果defender是多态的(所以你可以使用Identity仿函数而不是Const),并且对于参数是多态的,你需要Rank-2多态性。

虽然你并不需要担心这种类别理论魔术,因为镜头库提供了易于使用的同义词。只要写签名,你应该始终反正

offend :: Traversal' Game Army -> Traversal' Game Army -> State Game BattleResult 

,并得到适当的多态参数。啊,当然你需要-XRankNTypes扩展。实际上并不需要-XFlexibleContexts(这是完全无害的,但没有理由不使用它)。


&dagger;无论如何,如果你问我,Hindley-Milner会是一个奇迹,但它只是起作用,因为有一个明确定义的最常用的任何表达式的签名。 Rank-1代码只有这种情况:在Rank-N中,您可以随时在另一层普遍量化中折腾。编译器无法知道何时结束!

&ddagger;其实一个Getting,这是一个遍历getter。 GetterGetting之间的差异在于后者可能是部分的(这是必要的,因为您使用ix;编译器无法证明军队列表中索引1处实际上存在元素)。

+0

还有一个问题。当我尝试放置线路'defender.troops。= 0'在'return Win'之前的嵌套'do'块中,编译器会抛出一个错误,并建议再次使用'FlexibleContexts'。没有这个扩展,怎么做? – Leonid

+0

并且有什么比'preuse'更好的吗?我是否在适当的地方使用它? – Leonid

+1

真的吗?嵌套'do'块在这里应该没什么区别......但是如果它让编译器很开心,就可以打开'-XFlexibleContexts',正如我所说我没有理由不这样做。 - 'preuse'在这里没问题,这也是我用的。 – leftaroundabout