我最近在Hackage上发现了lens包,并尝试在一个小测试项目中使用它,如果我继续努力,这个项目可能会变成一个MUD/MUSH服务器。
下面是我的代码的简化版本,展示了我目前遇到的问题,这些问题涉及用于访问键/值容器(在我的情况下是Data.Map.Strict)的at lens。
{-# 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
由于房间、玩家和类似的对象在代码中被引用,因此我将它们存储在我的World状态类型中,作为Id(新类型UUID)到它们的数据对象的映射。
为了使用lenses检索这些对象,我需要以某种方式处理at lens返回的Maybe(如果键不在map中,则为Nothing)。在我的最后一行中,我尝试通过traverse来解决这个问题,只要最终结果是Monoid的实例,它就可以进行类型检查,但这通常不是这种情况。在这里,它不是因为playerLocation返回一个没有Monoid实例的RoomId。
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'
由于traverse只需要Monoid来泛化大于1的容器,所以我现在想知道是否有更好的方法来处理这个问题,而不需要在我要存储在map中的所有类型可能包含的对象上使用语义上无意义的Monoid实例。
或者也许我完全误解了这个问题,我需要使用一个完全不同的非常庞大的镜头包?
Data.Monoid
中的First
或Last
单子怎么样? - Nathan Howell