在索引容器中使用 return 值缩放 StateT
Zooming StateT with return value in indexed container
我无法找出最干净的方法来缩放像 StateT 这样的效果,returns 一个值到一个索引容器,如矢量或地图。
例如,假设我有一些纸牌游戏的结构:
data Card = Card
{ cardValue :: Int
} deriving (Show, Eq)
makeFields ''Card
data Player = Player
{ playerCards :: [Card]
} deriving (Show, Eq)
makeFields ''Player
data Game = Game
{ gamePlayers :: M.Map Int Player
} deriving (Show, Eq)
makeFields ''Game
data Action = GiveCard Card | DoNothing
以及一个处理玩家在回合中使用 StateT 效果的移动的函数:
playerAction :: (MonadIO m) => StateT Player m Action
playerAction = do
cards' <- use cards
case cards' of
(c:rest) -> GiveCard c <$ (cards .= rest)
_ -> return DoNothing
我想做的是在游戏中的玩家内部建立索引,并将这个 StateT 应用到那个玩家。看起来像这样的东西:
gameAction :: (MonadIO m) => Int -> StateT Game m ()
gameAction i = do
Just action <- zoom (players . at i . mapJust) playerAction
case action of
GiveCard c -> liftIO $ print c
DoNothing -> liftIO $ putStrLn "Doing nothing"
在遍历中添加_Just
或将at i
替换为ix i
导致此编译错误:
• Could not deduce (Monoid Action) arising from a use of ‘_Just’
from the context: MonadIO m
bound by the type signature for:
gameAction :: forall (m :: * -> *).
MonadIO m =>
Int -> StateT Game m ()
at src/MainModule.hs:36:1-52
• In the second argument of ‘(.)’, namely ‘_Just’
In the second argument of ‘(.)’, namely ‘at i . _Just’
In the first argument of ‘zoom’, namely ‘(players . at i . _Just)’
|
38 | action <- zoom (players . at i . _Just) playerAction
| ^^^^^
我可以将 non
与虚拟播放器值一起使用,但如果索引不存在,那么它会在虚拟值上静默运行函数,这不是我想要的:
emptyPlayer :: Player
emptyPlayer = Player []
gameAction :: (MonadIO m) => Int -> StateT Game m ()
gameAction i = do
action <- zoom (players . at i . non emptyPlayer) playerAction
case action of
GiveCard c -> liftIO $ print c
DoNothing -> liftIO $ putStrLn "Doing nothing"
我可以用preuse
抓取播放器,修改它并设置修改后的值。调用执行此操作的函数非常冗长,因为它必须接受 runMonad 函数以及 getter 和 setter 镜头。
prezoom run get set m = do
maybeS <- preuse get
case maybeS of
Just s -> do
(r, s') <- lift $ run m s
set .= s'
return $ Just r
Nothing -> return Nothing
gameAction :: (MonadIO m) => Int -> StateT Game m ()
gameAction i = do
Just action <- prezoom runStateT (players . ix i) (players . ix i) playerAction
case action of
GiveCard c -> liftIO $ print c
DoNothing -> liftIO $ putStrLn "Doing nothing"
我不太喜欢上述放大索引容器的方法。有没有更简单、更简洁的方法来做到这一点?
你想要它做什么?碰撞? zoom (players . singular (ix i))
会这样做。
听起来您已经掌握了潜在的语义问题,但为了清楚起见,让我重申一下。
at i
是一个 Lens
到一个 return 是一个 Maybe
的容器,因为该项目可能从容器中丢失(也许索引超出了末尾名单)。将这样的 Lens
与像 _Just
这样的 Prism
组合在一起会将整个事情变成 Traversal
:
players . at i . _Just :: Traversal' Game Player
现在,zoom
可以与 Traversal
一起使用,但它需要一个 Monoid
作为有状态操作的 return 值。来自 the docs:
When applied to a Traversal'
over multiple values, the actions for each target are executed sequentially and the results are aggregated.
A Traversal
可能 return 零个或多个结果,因此 zoom
将执行 monadic 操作零次或多次,将 mempty
填写为默认值并将多个结果与 mappend
组合。该文档还具有以下针对 zoom
的专用类型签名,它演示了 Monoid
约束:
zoom :: (Monad m, Monoid c) => Traversal' s t -> StateT t m c -> StateT s m c
这就是为什么您的错误消息显示“无法推断 (Monoid Action)
”:playerAction
returns an Action
and zoom
needs a Monoid
对于 Action
因为你给了它一个 Traversal
.
因此解决方法是从有状态操作中选择 Monoid
到 return。我们知道 Traversal
将命中一个或零个目标 - at i
永远不会 return 多个结果 - 所以我们正在寻找的 Monoid
的正确语义是“第一个结果或失败”。 Monoid
就是 First
。 (我们不必担心会丢弃额外的结果,因为不会有任何结果。)
action <- getFirst <$> zoom (players . at i . _Just) (fmap (First . Just) playerAction)
-- here action :: Maybe Action
(我在 phone 上,所以我还没有测试这段代码!)你可以使用 ala
.
稍微清理一下
我无法找出最干净的方法来缩放像 StateT 这样的效果,returns 一个值到一个索引容器,如矢量或地图。
例如,假设我有一些纸牌游戏的结构:
data Card = Card
{ cardValue :: Int
} deriving (Show, Eq)
makeFields ''Card
data Player = Player
{ playerCards :: [Card]
} deriving (Show, Eq)
makeFields ''Player
data Game = Game
{ gamePlayers :: M.Map Int Player
} deriving (Show, Eq)
makeFields ''Game
data Action = GiveCard Card | DoNothing
以及一个处理玩家在回合中使用 StateT 效果的移动的函数:
playerAction :: (MonadIO m) => StateT Player m Action
playerAction = do
cards' <- use cards
case cards' of
(c:rest) -> GiveCard c <$ (cards .= rest)
_ -> return DoNothing
我想做的是在游戏中的玩家内部建立索引,并将这个 StateT 应用到那个玩家。看起来像这样的东西:
gameAction :: (MonadIO m) => Int -> StateT Game m ()
gameAction i = do
Just action <- zoom (players . at i . mapJust) playerAction
case action of
GiveCard c -> liftIO $ print c
DoNothing -> liftIO $ putStrLn "Doing nothing"
在遍历中添加_Just
或将at i
替换为ix i
导致此编译错误:
• Could not deduce (Monoid Action) arising from a use of ‘_Just’
from the context: MonadIO m
bound by the type signature for:
gameAction :: forall (m :: * -> *).
MonadIO m =>
Int -> StateT Game m ()
at src/MainModule.hs:36:1-52
• In the second argument of ‘(.)’, namely ‘_Just’
In the second argument of ‘(.)’, namely ‘at i . _Just’
In the first argument of ‘zoom’, namely ‘(players . at i . _Just)’
|
38 | action <- zoom (players . at i . _Just) playerAction
| ^^^^^
我可以将 non
与虚拟播放器值一起使用,但如果索引不存在,那么它会在虚拟值上静默运行函数,这不是我想要的:
emptyPlayer :: Player
emptyPlayer = Player []
gameAction :: (MonadIO m) => Int -> StateT Game m ()
gameAction i = do
action <- zoom (players . at i . non emptyPlayer) playerAction
case action of
GiveCard c -> liftIO $ print c
DoNothing -> liftIO $ putStrLn "Doing nothing"
我可以用preuse
抓取播放器,修改它并设置修改后的值。调用执行此操作的函数非常冗长,因为它必须接受 runMonad 函数以及 getter 和 setter 镜头。
prezoom run get set m = do
maybeS <- preuse get
case maybeS of
Just s -> do
(r, s') <- lift $ run m s
set .= s'
return $ Just r
Nothing -> return Nothing
gameAction :: (MonadIO m) => Int -> StateT Game m ()
gameAction i = do
Just action <- prezoom runStateT (players . ix i) (players . ix i) playerAction
case action of
GiveCard c -> liftIO $ print c
DoNothing -> liftIO $ putStrLn "Doing nothing"
我不太喜欢上述放大索引容器的方法。有没有更简单、更简洁的方法来做到这一点?
你想要它做什么?碰撞? zoom (players . singular (ix i))
会这样做。
听起来您已经掌握了潜在的语义问题,但为了清楚起见,让我重申一下。
at i
是一个 Lens
到一个 return 是一个 Maybe
的容器,因为该项目可能从容器中丢失(也许索引超出了末尾名单)。将这样的 Lens
与像 _Just
这样的 Prism
组合在一起会将整个事情变成 Traversal
:
players . at i . _Just :: Traversal' Game Player
现在,zoom
可以与 Traversal
一起使用,但它需要一个 Monoid
作为有状态操作的 return 值。来自 the docs:
When applied to a
Traversal'
over multiple values, the actions for each target are executed sequentially and the results are aggregated.
A Traversal
可能 return 零个或多个结果,因此 zoom
将执行 monadic 操作零次或多次,将 mempty
填写为默认值并将多个结果与 mappend
组合。该文档还具有以下针对 zoom
的专用类型签名,它演示了 Monoid
约束:
zoom :: (Monad m, Monoid c) => Traversal' s t -> StateT t m c -> StateT s m c
这就是为什么您的错误消息显示“无法推断 (Monoid Action)
”:playerAction
returns an Action
and zoom
needs a Monoid
对于 Action
因为你给了它一个 Traversal
.
因此解决方法是从有状态操作中选择 Monoid
到 return。我们知道 Traversal
将命中一个或零个目标 - at i
永远不会 return 多个结果 - 所以我们正在寻找的 Monoid
的正确语义是“第一个结果或失败”。 Monoid
就是 First
。 (我们不必担心会丢弃额外的结果,因为不会有任何结果。)
action <- getFirst <$> zoom (players . at i . _Just) (fmap (First . Just) playerAction)
-- here action :: Maybe Action
(我在 phone 上,所以我还没有测试这段代码!)你可以使用 ala
.