Объектив для получения или установки поля записи, определяемого аргументом времени выполнения - PullRequest
1 голос
/ 06 июня 2019

У меня есть эти типы (и больше):

data Player = PlayerOne | PlayerTwo deriving (Eq, Show, Read, Enum, Bounded)

data Point = Love | Fifteen | Thirty deriving (Eq, Show, Read, Enum, Bounded)

data PointsData =
  PointsData { pointsToPlayerOne :: Point, pointsToPlayerTwo :: Point }
  deriving (Eq, Show, Read)

Я занимаюсь Теннисным ката , и в качестве части реализации я хотел бы использовать некоторые функции, которые позволяют мне получать или устанавливать очки для произвольного игрока, известного только во время выполнения. .

Формально мне нужны такие функции:

pointFor :: PointsData -> Player -> Point
pointFor pd PlayerOne = pointsToPlayerOne pd
pointFor pd PlayerTwo = pointsToPlayerTwo pd

pointTo :: PointsData -> Player -> Point -> PointsData
pointTo pd PlayerOne p = pd { pointsToPlayerOne = p }
pointTo pd PlayerTwo p = pd { pointsToPlayerTwo = p }

Как показано, моя проблема не в том, что я не могу реализовать эти функции.

Они, однако, выглядят подобными линзе , поэтому мне интересно, смогу ли я получить эту функциональность через библиотеку lens?

В большинстве учебных пособий по объективам показано, как получить или установить конкретную именованную часть более крупной структуры данных. Это не совсем подходит для того, что я пытаюсь сделать здесь; скорее я пытаюсь получить или установить часть, определенную во время выполнения.

Ответы [ 3 ]

4 голосов
/ 06 июня 2019

Экскурсия в несколько абстрактных классов типов. Ваш PointsData имеет особые отношения с типом Player. Это немного похоже на Map Player Point, с учетом того, что для каждое возможное значение Player, всегда соответствует Point. В каком-то смысле, PointsData подобен «ограниченной функции» Player -> Point.

Если мы сделаем PointsData полиморфным для типа Points, он будет соответствовать классу типов Representable. Мы бы сказали, что PointsData «представлен» Player.

Representable часто используется в качестве интерфейса для табличных данных, как в пакете grid .


Таким образом, одним из возможных решений было бы превратить PointsData в фактический Map, но скрыть реализацию за умным конструктором, который взял функцию Player -> Point, чтобы инициализировать ее для всех возможных ключей (это будет соответствовать tabulate метод Representable).

Пользователь не должен иметь возможность удалять ключи с карты. Но мы могли бы использовать Ixed экземпляр Map для обеспечения прохождения.

import Control.Lens
import Data.Map.Strict -- from "containers"

newtype PointsData = PointsData { getPoints :: Map Player Point } 

init :: (Player -> Point) -> PointsData
init f = PointsData (Data.Map.Strict.fromList ((\p -> (p, f p)) <$> [minBound..maxBound]))


playerPoints :: Player -> Lens' PointsData Point
playerPoints pl = Control.Lens.singular (iso getPoints PointsData . ix pl)
4 голосов
/ 06 июня 2019

Вы можете создать функцию, которая производит Lens с Player, например:

playerPoints :: Player -> Lens' PointsData Point
playerPoints PlayerOne = field @"pointsToPlayerOne"
playerPoints PlayerTwo = field @"pointsToPlayerTwo"

(используется field из generic-lens)

Использование будет выглядеть следующим образом:

pts :: PointsData

pl1 = pts ^. playerPoints PlayerOne
pl2 = pts ^. playerPoints PlayerTwo

newPts = pts & playerPoints PlayerOne .~ 42

PS Или вы искали выбор поля PointsData, сопоставляя имя поля с Player именем конструктора?Это также возможно через Generic, но, похоже, не стоит проблем.

0 голосов
/ 06 июня 2019

Основываясь на ответе от Федора Сойкина и комментариях от duplode , я использовал makeLenses от объектив и письмофункция, которая возвращает соответствующий объектив:

data PointsData =
  PointsData { _pointsToPlayerOne :: Point, _pointsToPlayerTwo :: Point }
  deriving (Eq, Show, Read)
makeLenses ''PointsData

playerPoint :: Player -> Lens' PointsData Point
playerPoint PlayerOne = pointsToPlayerOne
playerPoint PlayerTwo = pointsToPlayerTwo

Может использоваться как фрагмент большей функции:

score :: Score -> Player -> Score
-- ..
score (Points pd) winner = Points $ pd & playerPoint winner %~ succ
-- ..
...