繁体   English   中英

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

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

我最近在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  

由于在整个代码中引用了房间,播放器和类似对象,因此我将它们存储在我的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'

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

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

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

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

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

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

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

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

简短的回答:镜头包不是魔术。

在没有告诉我错误或默认值是什么的情况下,您想要:

viewPlayerLocation :: World - > PlayerId - > RoomId

你知道两件事

要找回那些带镜头的人,我需要处理镜头所返回的Maybe

只要最终结果是Monoid的实例,遍历就会进行类型检查

使用Monoid ,当查找失败时,你会得到mempty :: Monoid m => m作为默认值。

什么可能失败: PlayerId不能在_worldPlayers_playerLocation不能在_worldRooms

那么,如果查找失败,您的代码应该怎么做? 这“不可能”吗? 如果是这样,那么使用fromMaybe (error "impossible") :: Maybe a -> a崩溃。

如果查找可能失败,那么是否有一个合理的默认值? 或许返回Maybe RoomId并让来电者决定?

^?! 这让你免于来自fromMaybe

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM