State Monad,随机数序列和monadic代码

时间:2009-12-24 03:32:05

标签: haskell monads state-monad do-notation

我正在努力掌握State Monad,为此我想编写一个使用线性同余生成器生成一系列随机数的monadic代码(可能不太好,但我的目的只是为了学习状态Monad,没有建立一个好的RNG库)。

生成器就是这样(为了简单起见,我想生成Bool的序列):

type Seed = Int

random :: Seed -> (Bool, Seed)
random seed = let (a, c, m) = (1664525, 1013904223, 2^32)  -- some params for the LCG
                  seed' = (a*seed + c) `mod` m
              in  (even seed', seed')   -- return True/False if seed' is even/odd 

不要担心数字,这只是种子的更新规则(根据Numerical Recipes)应该生成Int s的伪随机序列。现在,如果我想按顺序生成随机数,我会这样做:

rand3Bools :: Seed -> ([Bool], Seed)
rand3Bools seed0  = let (b1, seed1) = random seed0
                        (b2, seed2) = random seed1
                        (b3, seed3) = random seed2
                    in  ([b1,b2,b3], seed3)

好的,所以我可以通过使用State Monad避免这个样板:

import Control.Monad.State

data Random {seed :: Seed, value :: Bool}

nextVal = do 
   Random seed val <- get 
   let seed' = updateSeed seed
       val'  = even seed'
   put (Random seed' val')
   return val'

updateSeed seed = let (a,b,m) = (1664525, 1013904223, 2^32) in (a*seed + c) `mod` m

最后:

getNRandSt n = replicateM n nextVal 

getNRand :: Int -> Seed -> [Bool]
getNRand   n seed = evalState (getNRandStates n) (Random seed True)

好的,这很好用,给我一个每个给定种子的n个伪随机Bool列表。但...

我可以阅读我所做的事情(主要基于这个例子:http://www.haskell.org/pipermail/beginners/2008-September/000275.html)并复制它以做其他事情。但我不认为我能理解在do-notation和monadic函数背后发生的事情(比如replicateM)。

任何人都可以帮我解决一些疑惑吗?

1 - 我试图去掉nextVal函数来理解它的作用,但我做不到。我可以猜测它会提取当前状态,更新它然后将状态提前传递到下一个计算,但这只是基于读取这个do-sugar,就像它是英语一样。

我如何将此功能真正用于原始&gt;&gt; =并逐步返回功能?

2 - 我无法理解putget函数究竟是做什么的。我猜他们会“打包”并“打开”状态。但是糖的背后的机制对我来说仍然是难以捉摸的。

嗯,关于此代码的任何其他一般性评论都非常受欢迎。我有时会遇到Haskell,我可以创建一个有效的代码并按照我的期望去做,但是我不能“按照评估”进行操作,因为我习惯于使用命令式程序。

3 个答案:

答案 0 :(得分:31)

答案 1 :(得分:8)

首先,您的示例过于复杂,因为它不需要将val存储在状态monad中;只有种子才是持久状态。其次,我认为如果不使用标准状态monad,你将获得更好的运气,你可以用它们的类型重新实现所有状态monad及其操作。我想你会通过这种方式学到更多东西。以下是一些让您入门的声明:

data MyState s a = MyState (s -> (s, b))

get :: Mystate s s
put :: s -> Mystate s ()

然后你可以写自己的连词:

unit :: a -> Mystate s a
bind :: Mystate s a -> (a -> Mystate s b) -> Mystate s b

最后

data Seed = Seed Int
nextVal :: Mystate Seed Bool

至于你的烦恼,你使用的do符号非常复杂。 但是desugaring是一种一次性的机械程序。尽可能接近我的代码,你的代码应该像这样desugar(回到原来的类型和代码,我不同意):

 nextVal = get >>= \ Random seed val ->
                      let seed' = updateSeed seed
                          val'  = even seed'
                      in  put (Random seed' val') >>= \ _ -> return val'

为了使嵌套结构更加清晰,我对缩进采取了重大的自由。

答案 2 :(得分:5)

你有几个很棒的回复。在使用状态monad时我所做的就是用State s a替换s -> (s,a)(毕竟,这就是它的真实含义)。

然后,您将获得一个类似于绑定的类型:

(>>=) :: (s -> (s,a)) ->
         (a -> s -> (s,b)) ->
         (s -> (s,b))

你看到bind只是一种特殊的函数组合运算符,比如(.)

我写了关于州monad here的博客/教程。它可能不是特别好,但通过编写它帮助我把事情搞得更好。