我正在努力掌握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 - 我无法理解put
和get
函数究竟是做什么的。我猜他们会“打包”并“打开”状态。但是糖的背后的机制对我来说仍然是难以捉摸的。
嗯,关于此代码的任何其他一般性评论都非常受欢迎。我有时会遇到Haskell,我可以创建一个有效的代码并按照我的期望去做,但是我不能“按照评估”进行操作,因为我习惯于使用命令式程序。
答案 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的博客/教程。它可能不是特别好,但通过编写它帮助我把事情搞得更好。