gpt4 book ai didi

haskell - 使用索引容器中的返回值缩放 StateT

转载 作者:行者123 更新时间:2023-12-02 12:53:40 26 4
gpt4 key购买 nike

我无法找出缩放效果(例如 StateT)的最简洁方法,该效果将值返回到矢量或 map 等索引容器中。

例如,假设我有一些纸牌游戏的结构:

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 与虚拟 Player 值一起使用,但如果索引不存在,那么它会在虚拟值上静默运行该函数,这不是我想要的:

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"

我不太喜欢上述放大索引容器的方法。有没有更简单、更干净的方法来做到这一点?

最佳答案

听起来您已经掌握了潜在的语义问题是什么,但为了清楚起见,让我重申一下。

at i 是容器中的一个Lens,它返回一个Maybe,因为该项目可能从容器中丢失(可能是索引)超出列表末尾)。将这样的 Lens 与像 _Just 这样的 Prism 组合起来,会将整个事情变成Traversal:

players . at i . _Just :: Traversal' Game Player

现在,zoom 可以与 Traversal 一起使用,但它需要一个 Monoid 作为有状态操作的返回值。来自 the docs :

When applied to a Traversal' over multiple values, the actions for each target are executed sequentially and the results are aggregated.

遍历可能会返回零个或多个结果,因此zoom将执行单子(monad)操作零次或多次,并填充mempty 作为默认值,并使用 mappend 组合多个结果。这些文档还具有以下用于 zoom 的专用类型签名,它演示了 Monoid 约束:

zoom :: (Monad m, Monoid c) => Traversal' s t -> StateT t m c -> StateT s m c

这就是为什么您的错误消息显示“无法推断 (Monoid Action)”:playerAction 返回 Actionzoom 需要一个 Monoid 来执行 Action,因为您给了它一个 Traversal

因此解决方法是选择一个 Monoid 从有状态操作返回。我们知道遍历将命中一个或零个目标 - at i 永远不会返回多个结果 - 因此我们的 Monoid 的正确语义我们正在寻找的是“要么首先结果,要么失败”。 MonoidFirst 。 (我们不需要担心丢弃额外的结果,因为不会有任何结果。)

action <- getFirst <$> zoom (players . at i . _Just) (fmap (First . Just) playerAction)
-- here action :: Maybe Action

(我在手机上,所以我还没有测试过这段代码!)您也许可以使用 ala 来清理一下。

关于haskell - 使用索引容器中的返回值缩放 StateT,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48760201/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com