I recently discovered the lens package on Hackage and have been trying to make use of it now in a small test project that might turn into a MUD/MUSH server one very distant day if I keep working on it.
Here is a minimized version of my code illustrating the problem I am facing right now with the at lenses used to access Key/Value containers (Data.Map.Strict in my case)
{-# 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
Since rooms, players and similar objects are referenced all over the code I store them in my World state type as maps of Ids (newtyped UUIDs) to their data objects.
To retrieve those with lenses I need to handle the Maybe returned by the at lens (in case the key is not in the map this is Nothing) somehow. In my last line I tried to do this via traverse which does typecheck as long as the final result is an instance of Monoid but this is not generally the case. Right here it is not because playerLocation returns a RoomId which has no Monoid instance.
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'
Since the Monoid is required by traverse only because traverse generalizes to containers of sizes greater than one I was now wondering if there is a better way to handle this that does not require semantically nonsensical Monoid instances on all types possibly contained in one my objects I want to store in the map.
Or maybe I misunderstood the issue here completely and I need to use a completely different bit of the rather large lens package?
There is
^?!
which frees you from callingfromMaybe
.Short answer: the lens package is not magic.
Without telling me what the error or default is, you want to make:
You know two things, that
and
With a
Monoid
you getmempty :: Monoid m => m
as the default when the lookup fails.What can fail: The
PlayerId
can not be in the_worldPlayers
and the_playerLocation
can not be in the_worldRooms
.So what should your code do if a lookup fails? Is this "impossible" ? If so, then use
fromMaybe (error "impossible") :: Maybe a -> a
to crash.If it possible for the lookup to fail then is there a sane default? Perhaps return
Maybe RoomId
and let the caller decide?If you have a
Traversal
and you want to get aMaybe
for the first element, you can just useheadOf
instead ofview
, i.e.The infix version of
headOf
is called^?
. You can also usetoListOf
to get a list of all elements, and other functions depending on what you want to do. See theControl.Lens.Fold
documentation.A quick heuristic for which module to look for your functions in:
Getter
is a read-only view of exactly one valueLens
is a read-write view of exactly one valueTraversal
is a read-write view of zero-or-more valuesFold
is a read-only view of zero-or-more valuesSetter
is a write-only (well, modify-only) view of zero-or-more values (possibly uncountably many values, in fact)Iso
is, well, an isomorphism -- aLens
that can go in either directionIndexed
function, so you can look in the correspondingIndexed
moduleThink about what you're trying to do and what the most general module to put it in would be. :-) In this case you have a
Traversal
, but you're only trying to view, not modify, so the function you want is in.Fold
. If you also had the guarantee that it was referring to exactly one value, it would be in.Getter
.