我正在编写一些代码(围绕着打牌策略),它将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对其他人的文章越多,我就越不确定自己在做什么……
我应该如何将随机性和State巧妙地结合起来,以及如何正确地将它们命名为?
谢谢!
编辑:供参考,吉突布上的全电流源。
发布于 2017-10-18 06:32:14
不如举个例子来帮你。由于您没有发布完整的工作代码片段,所以我将替换大量操作,并演示如何评估monads:
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这里要注意的是,您首先要“解开”外部单片。因此,如果您有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)https://stackoverflow.com/questions/46803597
复制相似问题