如何在没有Monoid实例的Control.Lens.Indexed中处理at的结果

Mat*_*ann 15 haskell lenses haskell-lens

我最近在Hackage上发现了镜头包,并且现在正试图在一个小的测试项目中使用它,如果我继续工作,它可能会在一个非常遥远的日子变成MUD/MUSH服务器.

这是我的代码的最小化版本,说明我现在遇到的问题,用于访问键/值容器的at镜头(在我的情况下是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  
Run Code Online (Sandbox Code Playgroud)

由于在整个代码中引用了房间,播放器和类似对象,因此我将它们存储在我的World状态类型中,作为其数据对象的Ids(newtyped UUID)映射.

要检索那些带镜头的人,我需要处理由镜头返回的Maybe(如果键不在地图中,这是什么都没有).在我的最后一行中,我尝试通过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'
Run Code Online (Sandbox Code Playgroud)

由于遍历需要使用Monoid只是因为遍历推广到大于1的容器,我现在想知道是否有更好的方法来处理这个问题,并不需要在我想要的一个对象中可能包含的所有类型的语义无意义的Monoid实例存储在地图中.

或许我完全误解了这个问题,我需要使用一个完全不同的相当大的镜头包?

sha*_*haf 22

如果你有一个Traversal并且你想要获得Maybe第一个元素,你可以使用headOf而不是view,即

viewPlayerLocation :: World -> PlayerId -> Maybe RoomId
viewPlayerLocation world playerId =
  headOf (worldPlayers.at playerId.traverse.playerLocation) world  
Run Code Online (Sandbox Code Playgroud)

中缀版本headOf被称为^?.您还可以使用toListOf获取所有元素的列表,以及其他功能,具体取决于您要执行的操作.请参阅Control.Lens.Fold文档.

一个快速启发式,用于查找函数的模块:

  • A Getter是只有一个值的只读视图
  • A Lens是一个值的读写视图
  • A Traversal是零或更多值的读写视图
  • A Fold是零或更多值的只读视图
  • A Setter是零或更多值的只写(仅限修改)视图(实际上可能是多个值)
  • 一个Iso是同构 - 一个Lens可以向两个方向发展的同构
  • 大概你知道什么时候使用一个Indexed函数,所以你可以查看相应的Indexed模块

想想你想要做什么以及最常用的模块是什么.:-)在这种情况下你有一个Traversal,但你只是想查看,而不是修改,所以你想要的功能.Fold.如果你还保证它只是指一个值,它就会在.Getter.