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
但我已经不舒服了,而且不知道如何称呼它,例如将 evalRand
与 execState
等结合使用。与其他人相比,我对 MonadRandom
、RandGen
和 mtl
的了解越多,我就越不确定自己在做什么我在做...
我应该如何巧妙地结合随机性和 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)
我正在编写一些同时使用 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
以及如何正确调用它们?
谢谢!
编辑:供参考,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)