Haskell - 镜头,使用'to'功能

Dwi*_*son 9 haskell haskell-lens

我有以下代码.我希望能够在给定游戏状态时修改活动玩家的生命.我想出了一个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``
Run Code Online (Sandbox Code Playgroud)

和有问题的代码:

{-# 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)
Run Code Online (Sandbox Code Playgroud)

每个玩家按顺序轮到他们.我需要同时跟踪所有玩家以及当前活跃的玩家,这就是我如何构建的原因,尽管我对不同的结构持开放态度,因为我可能还没有合适的玩家.

J. *_*son 12

当您在镜头库中组合各种项目时,(.)它们可能会根据一种子类型丢失功能(见下文).在这种情况下,你已经由Lens(players)与Getter(to f对某些功能f),因此组合仅仅是一个Getter,而over在镜头,既可以获取和设置行为.

activePlayer但是,应该形成一个有效的镜头,所以你可以手动将其作为一个吸气剂/设定器对来编写.我假设索引永远无效,我在下面部分写它.

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) }
Run Code Online (Sandbox Code Playgroud)

为了更好地理解lens库中发生的子类型,我们可以使用Hackage中的Big Lattice Diagram

Hackage的大格子图

无论何时将两种镜头类型组合在一起,(.)最终会得到该图表中的第一个共同后代.所以,如果你结合起来Lens,Prism你可以看到他们的箭头汇聚在一起Traversal.如果合并LensGetter(其中to f为),那么你得到一个Getter,因为Getter是直接后裔Lens.