运行 这种具有状态和随机性概念的单子计算
Run this monadic computation with notion of state and randomness
我描述了以下计算:
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
一半的时间。
简而言之:
evalRandIO $ evalStateT bar False
让我们把这个分开。 bar
需要能够保持 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
变体,因为它实际上也是一个状态 monad;它在幕后包含某种类型的 class 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
产生 Bool
。 运行它几次:
ghci> let barIO = getStdRandom . runRand $ evalStateT bar False
ghci> barIO
True
ghci> barIO
True
ghci> barIO
False
对我来说足够随机了。但是,,还有更短的方法。由于这是一个常见的用例,Control.Monad.Random
模块提供了一个快捷方式,evalRandIO
,它本质上是我们上面使用的 getStdRandom . runRand
。这是一个简单的替代品,以
结尾
evalRandIO $ evalStateT bar False
我描述了以下计算:
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
一半的时间。
简而言之:
evalRandIO $ evalStateT bar False
让我们把这个分开。 bar
需要能够保持 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
变体,因为它实际上也是一个状态 monad;它在幕后包含某种类型的 class 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
产生 Bool
。 运行它几次:
ghci> let barIO = getStdRandom . runRand $ evalStateT bar False
ghci> barIO
True
ghci> barIO
True
ghci> barIO
False
对我来说足够随机了。但是,Control.Monad.Random
模块提供了一个快捷方式,evalRandIO
,它本质上是我们上面使用的 getStdRandom . runRand
。这是一个简单的替代品,以
evalRandIO $ evalStateT bar False