How do I handle the Maybe result of at in Control.Lens.Indexed without a Monoid instance

haskell, haskell-lens, lenses

Solution

If you have a `Traversal` and you want to get a `Maybe` for the first element, you can just use `headOf` instead of `view`, i.e.

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

The infix version of `headOf` is called `^?`. You can also use `toListOf` to get a list of all elements, and other functions depending on what you want to do. See the `Control.Lens.Fold` documentation.

A quick heuristic for which module to look for your functions in:

- A `Getter` is a read-only view of exactly one value

- A `Lens` is a read-write view of exactly one value

- A `Traversal` is a read-write view of zero-or-more values

- A `Fold` is a read-only view of zero-or-more values

- A `Setter` is a write-only (well, modify-only) view of zero-or-more values (possibly uncountably many values, in fact)

- An `Iso` is, well, an isomorphism -- a `Lens` that can go in either direction

- Presumably you know when you're using an `Indexed` function, so you can look in the corresponding `Indexed` module

Think 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`.

Problem

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?

Original source