Подтвердить что ты не робот

Как обрабатывать результат Maybe из in Control.Lens.Indexed без экземпляра Monoid

Недавно я обнаружил пакет объективов в Hackage и пытался использовать его сейчас в небольшом тестовом проекте, который может превратиться в сервер MUD/MUSH в очень далекий день, если я продолжу работать над ним.

Вот сводная версия моего кода, иллюстрирующая проблему, с которой я столкнулся прямо сейчас, с линзами, используемыми для доступа к контейнерам Key/Value (Data.Map.Strict в моем случае)

{-# LANGUAGE OverloadedStrings, GeneralizedNewtypeDeriving, TemplateHaskell #-}
module World where
import Control.Applicative ((<$>),(<*>), pure)
import Control.Lens
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as DM
import Data.Maybe
import Data.UUID
import Data.Text (Text)
import qualified Data.Text as T
import System.Random (Random, randomIO)

newtype RoomId = RoomId UUID deriving (Eq, Ord, Show, Read, Random)
newtype PlayerId = PlayerId UUID deriving (Eq, Ord, Show, Read, Random)

data Room =
  Room { _roomId :: RoomId 
       , _roomName :: Text
       , _roomDescription :: Text
       , _roomPlayers :: [PlayerId]
       } deriving (Eq, Ord, Show, Read)

makeLenses ''Room

data Player =
  Player { _playerId :: PlayerId
         , _playerDisplayName :: Text
         , _playerLocation :: RoomId
         } deriving (Eq, Ord, Show, Read)

makeLenses ''Player

data World =
  World { _worldRooms :: Map RoomId Room
        , _worldPlayers :: Map PlayerId Player
        } deriving (Eq, Ord, Show, Read)

makeLenses ''World

mkWorld :: IO World
mkWorld = do
  r1 <- Room <$> randomIO <*> (pure "The Singularity") <*> (pure "You are standing in the only place in the whole world") <*> (pure [])
  p1 <- Player <$> randomIO <*> (pure "testplayer1") <*> (pure $ r1^.roomId)
  let rooms = at (r1^.roomId) ?~ (set roomPlayers [p1^.playerId] r1) $ DM.empty
      players = at (p1^.playerId) ?~ p1 $ DM.empty in do
    return $ World rooms players

viewPlayerLocation :: World -> PlayerId -> RoomId
viewPlayerLocation world playerId=
  view (worldPlayers.at playerId.traverse.playerLocation) world  

Поскольку комнаты, игроки и подобные объекты ссылаются по всему коду, я храню их в моем типе состояния мира, как карты идентификаторов (newtyped UUID) для своих объектов данных.

Чтобы получить те, у кого есть линзы, мне нужно обработать элемент Maybe, возвращенный объективом (в случае, если ключ отсутствует на карте, это Nothing). В моей последней строке я пытался сделать это через траверс, который выполняет typecheck, пока конечный результат является экземпляром Monoid, но это не так. Прямо здесь это не потому, что playerLocation возвращает RoomId, который не имеет экземпляра Monoid.

No instance for (Data.Monoid.Monoid RoomId)
  arising from a use of `traverse'
Possible fix:
  add an instance declaration for (Data.Monoid.Monoid RoomId)
In the first argument of `(.)', namely `traverse'
In the second argument of `(.)', namely `traverse . playerLocation'
In the second argument of `(.)', namely
  `at playerId . traverse . playerLocation'

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

Или, может быть, я неправильно понял проблему здесь, и мне нужно использовать совершенно другой бит довольно большого пакета объективов?

4b9b3361

Ответ 1

Если у вас есть Traversal, и вы хотите получить Maybe для первого элемента, вы можете просто использовать headOf вместо view, т.е.

viewPlayerLocation :: World -> PlayerId -> Maybe RoomId
viewPlayerLocation world playerId =
  headOf (worldPlayers.at playerId.traverse.playerLocation) world  

Инфиксная версия headOf называется ^?. Вы также можете использовать toListOf для получения списка всех элементов и других функций в зависимости от того, что вы хотите сделать. См. Документацию Control.Lens.Fold.

Быстрая эвристика, для которой модуль ищет ваши функции в:

  • A Getter - это только чтение только одного значения
  • A Lens - это представление чтения и записи только одного значения
  • A Traversal - это представление чтения и записи значений нуля или более
  • A Fold - это представление только для чтения значений нуля или более
  • A Setter - это представление только для записи (ну, только для модификации) значений нуля или более (возможно, несчетно много значений)
  • An Iso является, ну, изоморфизмом - a Lens, который может идти в любом направлении
  • Предположительно вы знаете, когда используете функцию Indexed, поэтому вы можете посмотреть в соответствующем модуле Indexed

Подумайте о том, что вы пытаетесь сделать, и о том, какой будет наиболее общий модуль для его включения.:-) В этом случае у вас есть Traversal, но вы только пытаетесь просмотреть, а не изменять, поэтому требуемая функция находится в .Fold. Если у вас также есть гарантия, что он имеет в виду только одно значение, он будет находиться в .Getter.

Ответ 2

Короткий ответ: пакет объектива не волшебный.

Не сообщая мне, что такое ошибка или значение по умолчанию, вы хотите сделать:

viewPlayerLocation:: World → PlayerId → RoomId

Вы знаете две вещи, которые

Чтобы получить те, у которых есть линзы, мне нужно обработать элемент Maybe, возвращенный объективом

и

который выполняет typecheck, пока конечный результат является экземпляром Monoid

С Monoid вы получаете mempty :: Monoid m => m как значение по умолчанию, если поиск не работает.

Что может произойти: PlayerId не может находиться в _worldPlayers, а _playerLocation не может быть в _worldRooms.

Итак, что должен делать ваш код, если сбой поиска? Это "невозможно"? Если это так, используйте fromMaybe (error "impossible") :: Maybe a -> a для сбоя.

Если возможно, чтобы поиск завершился с ошибкой, есть ли нормальный стандарт? Возможно, верните Maybe RoomId и позвольте собеседнику решить?

Ответ 3

Существует ^?!, который освобождает вас от вызова fromMaybe.