[英]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
是一個只讀一個值的只讀視圖 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.