Question

I have the following code. I'd like to be able to modify the active player's life when given a game state. I came up with an activePlayer lens, but when I try and use it in combination with the -= operator I receive the following error:

> 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``

and the code in question:

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

Each player takes their turn in order. I need to keep track of all the players at once as well as which is currently active, which is the reason for how I structured that, although I am open to different structures since I probably don't have the right one yet.

Était-ce utile?

La solution

When you compose various items in the lens library with (.) they may lose capabilities according to a kind of subtyping (see below). In this case, you've composed a Lens (players) with a Getter (to f for some function f) and thus the combination is just a Getter while over acts on lenses that can both get and set.

activePlayer should form a valid lens, though, so you can just write it manually as a getter/setter pair. I'm writing it partially below under the assumption that the index can never be invalid.

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) }

To better understand the subtyping that's occurring in the lens library we can use the Big Lattice Diagram from Hackage

the Big Lattice Diagram from Hackage

Whenever you combine two lens types with (.) you end up with their first common descendent in that chart. So if you combine Lens and Prism you can see that their arrows converge on Traversal. If you combine Lens and Getter (of which to f is) then you get a Getter since Getter is a direct descendent of Lens.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top