1

我描述了以下计算:

import Control.Monad.State
import Control.Monad.Identity
import Control.Monad.Random.Class

-- * fair coin
fair :: MonadRandom m => m Bool
fair = (\p -> p <= 0.5) <$> getRandomR (0,1 :: Double)

-- * how do i run this 
bar :: (MonadState Bool m, MonadRandom m) => m Bool
bar = fair >>= \b -> put b >> return b

而且我想知道如何运行bar,以便在预期中bar评估到True一半的时间。

4

1 回答 1

3

简而言之:

evalRandIO $ evalStateT bar False

让我们把这个分开。bar需要能够将 a 保持Bool为状态,并且能够获取随机值。我们可以用任何一种方式嵌套它;在这里,我选择在随机数上分层状态,但你可以反过来做。

evalStateT具有此类型签名:

evalStateT :: Monad m => StateT s m a -> s -> m a

换句话说,给定一些需要在其他 monad 之上分层的状态的东西,它会实现该状态部分并根据底层 monad 为您提供该操作。名称的eval一部分意味着它将抛出结果状态并只给你值;还有execStateT一个为您提供结果状态并抛出输出,并runStateT为您提供两者的元组。我应该注意,无论如何,我们必须给它一个初始状态。您的代码不使用初始状态,所以我们也可以使用undefined,但我用False它来搞定它。

所以现在我们已经实现了状态位,我们还剩下什么?

ghci> :t evalStateT bar False
evalStateT bar False :: MonadRandom m => m Bool

它想要一个可以给它随机值的单子。好吧,我们有其中之一。Rand会做的。Rand也有run, eval, 和exec变体,因为它实际上也是一个状态单子;它在幕后拥有某种类型的值RandomGen。在这里,我们不想丢弃最后的状态,我们想保留结果,所以我们使用run变体。那么,我们现在有什么?

ghci> :t runRand (evalStateT bar False)
runRand (evalStateT bar False) :: RandomGen g => g -> (Bool, g)

为了比较:

ghci> :t random
random :: (RandomGen g, Random a) => g -> (a, g)

所以现在我们有一个简单的函数,它接受一个随机数生成器状态并吐出一对带有结果和一个新状态,就像random它自己来自System.Random. 我们如何使用它?好吧,挖掘System.Random模块,getStdRandom看起来很有用:

getStdRandom :: (StdGen -> (a, StdGen)) -> IO a

它采用我们所拥有的功能并将其转化为IO动作。(这是有道理IO的;它正在获取一些全局状态(即标准随机数生成器)并对其进行更新。)加上它之后我们有什么?

ghci> :t getStdRandom . runRand $ evalStateT bar False
getStdRandom . runRand $ evalStateT bar False :: IO Bool

正是我们所期望的:IO产生 a 的 a Bool。运行几次:

ghci> let barIO = getStdRandom . runRand $ evalStateT bar False
ghci> barIO
True
ghci> barIO
True
ghci> barIO
False

对我来说足够随机。然而,正如 Carsten 在评论中提到的那样,还有更短的方法。由于这是一个常见的用例,该Control.Monad.Random模块提供了一个快捷方式,evalRandIO,这本质上就是getStdRandom . runRand我们上面使用的。这是一个简单的替代品

evalRandIO $ evalStateT bar False
于 2015-12-17T05:40:49.190 回答