Недавно я обнаружил пакет объективов в 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 для всех типов, которые могут содержаться в одном моем объекты, которые я хочу сохранить на карте.
Или, может быть, я неправильно понял проблему здесь, и мне нужно использовать совершенно другой бит довольно большого пакета объективов?