我正在编写一些使用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
但我已经感到不舒服了,加上无法解决如何调用此问题,例如将evalRand
与execState
结合使用等。我在MonadRandom
,RandGen
和mtl
对其他人的阅读越多,我就越不确定我在做......
我应该如何巧妙地将Randomness和State
结合起来,我该如何恰当地称它们?
谢谢!
编辑:供参考,full current source on Github。
答案 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)