MonadRandom,State和monad变形金刚

时间:2017-10-18 05:51:22

标签: haskell 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结合起来,我该如何恰当地称它们?

谢谢!

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

1 个答案:

答案 0 :(得分:7)

那么一个例子来帮助你。由于您没有发布完整的工作代码段,因此我只会替换您的大部分操作并展示如何评估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

这里要注意的是你&#34;展开&#34;外面的monad首先。因此,如果您有RandT (StateT Reader ...) ...,那么您运行RandT(来自evalRandT或类似),然后是州,然后是读者。其次,你必须从外部monad lift使用内部monad上的操作。这可能看起来很笨拙,因为它非常笨拙。

我认识的最好的开发人员 - 那些我喜欢看和使用的代码 - 提取monad操作并提供所有原语完整的API,所以我不需要考虑monad的结构。 #39;思考我写作的逻辑结构。

在这种情况下(由于我在没有任何应用领域,押韵或理由的情况下编写上述内容,因此可能会略有做法),您可以写下:

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)