MonadRandom、State 和 monad 转换器

MonadRandom, State and monad transformers

我正在编写一些同时使用 State 和递归的代码(围绕打牌策略)。也许这部分实际上不需要(对我来说已经感觉很笨拙,即使作为一个相对初学者),但还有其他部分可能需要这样做所以我的一般问题是...

我最初的天真实现完全是确定性的(出价的选择只是函数 validBids 提供的第一个选项):

bidOnRound :: (DealerRules d) => d -> NumCards -> State ([Player], PlayerBids) ()
bidOnRound dealerRules cardsThisRound = do
  (players, bidsSoFar) <- get
  unless (List.null players) $ do
     let options = validBids dealerRules cardsThisRound bidsSoFar
     let newBid = List.head $ Set.toList options
     let p : ps = players
     put (ps, bidsSoFar ++ [(p, newBid)])
     bidOnRound dealerRules cardsThisRound

我称它为:

playGame :: (DealerRules d, ScorerRules s) => d -> s -> StateT Results IO ()
  ...
let (_, bidResults) = execState (bidOnRound dealerRules cardsThisRound) (NonEmpty.toList players, [])

现在我意识到我需要将随机性引入代码的这一部分和其他几个部分。不想到处乱扔 IO,也不想一直手动传递随机种子,我觉得我应该使用 MonadRandom 或其他东西 。我正在使用的图书馆使用它效果很好。这是一个明智的选择吗?

这是我尝试过的:

bidOnRound :: (DealerRules d, RandomGen g) => d -> NumCards -> RandT g (State ([Player], PlayerBids)) ()
bidOnRound dealerRules cardsThisRound = do
  (players, bidsSoFar) <- get
  unless (List.null players) $ do
     let options = Set.toList $ validBids dealerRules cardsThisRound bidsSoFar
     rnd <- getRandomR (0 :: Int, len options - 1)
     let newBid = options List.!! rnd
     let p : ps = players
     put (ps, bidsSoFar ++ [(p, newBid)])
     bidOnRound dealerRules cardsThisRound

但我已经不舒服了,而且不知道如何称呼它,例如将 evalRandexecState 等结合使用。与其他人相比,我对 MonadRandomRandGenmtl 的了解越多,我就越不确定自己在做什么我在做...

我应该如何巧妙地结合随机性和 State 以及如何正确调用它们?

谢谢!

编辑:供参考,full current source on Github

好吧,举个例子来帮助你。由于您没有 post 完整的工作代码片段,我将替换您的许多操作并展示如何评估 monad:

import Control.Monad.Trans.State
import Control.Monad.Random
import System.Random.TF

bidOnRound :: (RandomGen g) => Int -> RandT g (State ([Int], Int)) ()
bidOnRound i =
 do rand <- getRandomR (10,20)
    s <- lift $ get
    lift $ put ([], i + rand + snd s)

main :: IO ()
main =
 do g <- newTFGen
    print $ flip execState ([],1000) $ evalRandT (bidOnRound 100) g

这里要注意的是你首先 "unwrap" 外部 monad。所以如果你有 RandT (StateT Reader ...) ... 那么你 运行 RandT(例如通过 evalRandT 或类似的)然后是 reader 的状态。其次,您必须从外部 monad lift 才能对内部 monad 使用操作。这可能看起来很笨拙,那是因为它非常笨拙。

我认识的最好的开发人员——那些我喜欢查看和使用其代码的开发人员——提取 monad 操作并提供一个 API,所有原语都已完成,因此我不需要考虑结构当我在思考我正在编写的逻辑的结构时,monad。

在这种情况下(由于我在没有任何应用领域、押韵或原因的情况下写了上面的内容,所以会稍微做作)你可以这样写:

type MyMonad a = RandT TFGen (State ([Int],Int)) a

runMyMonad :: MyMonad () -> IO Int
runMyMonad f =
 do g <- newTFGen
    pure $ snd $ flip execState ([],1000) $ evalRandT f g

将Monad定义为简单的别名和执行操作,基本功能更容易:

flipCoin ::  MyMonad Int
flipCoin = getRandomR (10,20)

getBaseValue :: MyMonad Int
getBaseValue = snd <$> lift get

setBaseValue :: Int -> MyMonad ()
setBaseValue v = lift $ state $ \s -> ((),(fst s, v))

有了跑腿的工作,这通常是制作真实应用程序的一小部分,领域特定的逻辑更容易编写,当然也更容易阅读:

bidOnRound2 :: Int -> MyMonad ()
bidOnRound2 i =
 do rand <- flipCoin
    old  <- getBaseValue
    setBaseValue (i + rand + old)

main2 :: IO ()
main2 = print =<< runMyMonad (bidOnRound2 100)