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上读得越多,就越不确定自己在做什么。。。

我应该如何将Randomness和State巧妙地结合起来,以及如何正确地命名它们?

谢谢!

EDIT:供参考,Github上的完整当前源代码。

好吧,举个例子来帮你吧。由于您没有发布完整的工作代码片段,我将替换您的许多操作,并展示如何评估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

这里需要注意的是,您首先"打开"外部monad。因此,如果您有RandT (StateT Reader ...) ...,则运行RandT(例如通过evalRandT或类似程序),然后运行状态,然后运行读取器。其次,必须从外部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)

最新更新