Haskell - 镜头(Lenses),'to' 函数的使用

9

我有以下代码。当给定游戏状态时,我希望能够修改活跃玩家的生命值。我想到了使用activePlayer镜头,但是当我尝试将其与-=运算符结合使用时,会收到以下错误:

> over (activePlayer.life) (+1) initialState 
<interactive>:2:7:
    No instance for (Contravariant Mutator)
      arising from a use of `activePlayer'
    Possible fix:
      add an instance declaration for (Contravariant Mutator)
    In the first argument of `(.)', namely `activePlayer'
    In the first argument of `over', namely `(activePlayer . life)'
    In the expression: over (activePlayer . life) (+ 1) initialState``

需要进行翻译的代码如下:

{-# LANGUAGE TemplateHaskell #-}
module Scratch where

import Control.Lens
import Control.Monad.Trans.Class
import Control.Monad.Trans.State
import Data.Sequence (Seq)
import qualified Data.Sequence as S

data Game = Game
    { _players :: (Int, Seq Player) -- active player, list of players
    , _winners :: Seq Player
    }
    deriving (Show)

initialState = Game
    { _players = (0, S.fromList [player1, player2])
    , _winners = S.empty
    }

data Player = Player
    { _life :: Integer
    }
    deriving (Show, Eq)

player1 = Player
    { _life = 10
    }

player2 = Player
    { _life = 10
    }

makeLenses ''Game
makeLenses ''Player

activePlayer
  :: (Functor f, Contravariant f) =>
       (Player -> f Player) -> Game -> f Game
activePlayer = players.to (\(i, ps) -> S.index ps i)

每位玩家按顺序轮流行动。我需要同时跟踪所有玩家以及当前处于活跃状态的玩家,这就是我构建结构的原因,尽管我也可以采用不同的结构,因为我可能还没有找到正确的结构。


2
我认为你的问题在于 activePlayer 的定义允许它作为 getter,但不允许它作为 setter。你已经告诉它如何从 Sequence 中获取一个 player,但没有告诉它如何更改 active player,因此它不能用于修改 players。请查看 to 的类型 --- > :i to 的结果是 to :: (a -> c) -> Getter a b c d - Chris Taylor
1个回答

12
当您在镜头库中使用(.)组合各种项目时,它们可能会根据某种子类型而失去功能(见下文)。在这种情况下,您已经将一个Lensplayers)与一个Getterto f,其中f是某个函数)组合在一起,因此组合只是一个Getter,而over作用于既能获取又能设置的镜头。 activePlayer应该形成一个有效的镜头,因此您可以手动编写它作为getter/setter对。在假定索引永远不会无效的情况下,我在下面部分地编写它。
activePlayer :: Lens' Game Player
activePlayer = lens get set 
  where
    get :: Game -> Player
    get (Game { _players = (index, seq) }) = Seq.index seq index

    set :: Game -> Player -> Game
    set g@(Game { _players = (index, seq) }) player = 
      g { _players = (index, Seq.update index player seq) }

为了更好地理解在lens库中发生的子类型划分,我们可以使用Hackage上的大格图表

the Big Lattice Diagram from Hackage

每当你使用 (.) 结合两种镜头类型时,你最终得到的是它们在图表中的第一个共同后代。因此,如果你结合 LensPrism,你会发现它们的箭头汇聚于 Traversal。如果你结合 LensGetter(其中 to fGetter 的一部分),那么你将得到一个 Getter,因为 GetterLens 的直接后代。

谢谢,那个图现在确实有意义了。之前有点吓人。我认为可能有一个错别字,你的集合类型应该是 Game -> Player -> Game 对吧?至少,当我尝试 Player -> Game -> Player 时,它无法通过类型检查。 - Dwilson
那个图表看起来有点吓人,直到你需要它——是的,错别字就在那里。这就是我没有实际进行类型检查自己的代码所得到的结果。 :) - J. Abrahamson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接