Haskell — линзы, использование функции «to»

У меня есть следующий код. Я хотел бы иметь возможность изменять жизнь активного игрока при задании состояния игры. Я придумал объектив 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)

Каждый игрок делает свой ход по порядку. Мне нужно отслеживать всех игроков одновременно, а также тех, кто в данный момент активен, что является причиной того, как я это структурировал, хотя я открыт для разных структур, поскольку у меня, вероятно, еще нет подходящей.


person Dwilson    schedule 11.12.2013    source источник
comment
Я думаю, ваша проблема в том, что определение activePlayer позволяет ему действовать как геттер, но не как сеттер - вы сказали ему, как вытащить игрока из Последовательности, но не как изменить активного игрока - поэтому он нельзя использовать для модификации игроков. Проверьте тип to --- > :i to приводит к to :: (a -> c) -> Getter a b c d   -  person Chris Taylor    schedule 12.12.2013


Ответы (1)


Когда вы составляете различные элементы в библиотеке объективов с помощью (.), они могут терять возможности в соответствии с типом подтипа (см. ниже). В этом случае вы составили 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) }

Чтобы лучше понять подтипы, происходящие в библиотеке lens, мы можем использовать диаграмму Big Lattice Diagram от Hackage.

Большая решетчатая диаграмма из Hackage

Всякий раз, когда вы объединяете два типа линз с (.), вы получаете их первого общего потомка в этой таблице. Итак, если вы объедините Lens и Prism, вы увидите, что их стрелки сходятся на Traversal. Если вы объедините Lens и Getter (из которых to f), то вы получите Getter, так как Getter является прямым потомком Lens.

person J. Abrahamson    schedule 11.12.2013
comment
Спасибо, теперь эта схема имеет смысл. Раньше было страшно. Одна возможная опечатка, я думаю, что ваш тип набора должен быть Game -> Player -> Game правильным? По крайней мере, Player -> Game -> Player не вводил check, когда я пытался это сделать. - person Dwilson; 12.12.2013
comment
Эта диаграмма пугает до тех пор, пока она вам не понадобится — и да, именно опечатка. Это то, что я получаю за то, что на самом деле не проверяю свой собственный код. :) - person J. Abrahamson; 12.12.2013