State Monad,随机数字和一元代码的序列
我试图掌握State Monad,为此我想编写一个monadic代码,使用线性同余发生器生成一个随机数序列(可能不太好,但我的意图是学习State Monad,而不是建立一个好的RNG库)。
生成器就是这样(为了简单起见,我想生成一系列Bool
s):
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
不要担心数字,这只是种子的更新规则(根据数字食谱)应该生成Int
的伪随机序列。 现在,如果我想按顺序生成随机数字,我会这样做:
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
s列表给每个给定的种子。 但...
我可以阅读我所做的(主要基于此示例:http://www.haskell.org/pipermail/beginners/2008-September/000275.html)并复制它以完成其他操作。 但我不认为我能理解符号和单子函数背后真正发生的事情(如replicateM)。
任何人都可以帮助我解决这些疑惑吗?
1 - 我试着去解析下一个函数来理解它的功能,但我不能。 我猜可以提取当前状态,更新它,然后将状态传递给下一个计算,但这只是基于阅读这个do-sugar,就好像它是英文一样。
我如何真正解决这个功能的原始>> =和返回功能一步一步?
2 - 我无法理解put
和get
函数究竟做了什么。 我可以猜测他们“打包”和“解压缩”状态。 但do-sugar背后的机制仍然难以捉摸。
那么,关于这个代码的其他一般评论是非常受欢迎的。 我有时会跟Haskell一起合作,我可以创建一个可以工作的代码,并且做我期望的代码,但是我不能“跟随评估”,因为我习惯于使用命令式程序。
国家monad起初看起来有点混乱, 让我们按诺曼拉姆齐的建议做,并从头开始实施。 警告,这很长!
首先,State有两个类型参数:包含的状态数据的类型和最终计算结果的类型。 这里我们将分别使用stateData
和result
作为它们的类型变量。 如果你仔细想想,这是有道理的。 基于状态的计算的定义特征是它在产生输出的同时修改状态。
不太明显的是,类型构造函数从一个状态到一个修改后的状态和结果,如下所示:
newtype State stateData result = State (stateData -> (result, stateData))
因此,当monad称为“状态”时,monad包装的实际值是基于状态的计算值,而不是包含状态的实际值。
记住这一点,我们不应该惊讶地发现runState
函数用于在State monad中执行计算实际上只不过是包装函数本身的访问器,并且可以像这样定义:
runState (State f) = f
那么当你定义一个返回一个状态值的函数时,这意味着什么? 让我们暂时忽略一个事实,即国家是一个单子,只看基本类型。 首先,考虑这个函数(它实际上并没有对状态做任何事情):
len2State :: String -> State Int Bool
len2State s = return ((length s) == 2)
如果查看State的定义,我们可以看到这里的stateData
类型是Int
, result
类型是Bool
,所以数据构造函数包装的函数必须具有Int -> (Bool, Int)
。 现在,设想一个无状态版本的len2State
显然,它会有类型String -> Bool
。 那么如何将这样一个函数转换成一个返回适合于状态包装的值呢?
很明显,转换后的函数需要第二个参数, Int
代表状态值。 它还需要返回一个状态值,另一个Int
。 由于我们在这个函数中并没有对状态做任何事情,所以让我们做一些明显的事情 - 把这个int传递给它。 这是一个按照无状态版本定义的状态函数:
len2 :: String -> Bool
len2 s = ((length s) == 2)
len2State :: String -> (Int -> (Bool, Int))
len2State s i = (len2' s, i)
但那是愚蠢和多余的。 让我们概括一下转换,以便我们可以传递结果值,并将任何东西变成类似状态的函数。
convert :: Bool -> (Int -> (Bool, Int))
convert r d = (r, d)
len2 s = ((length s) == 2)
len2State :: String -> (Int -> (Bool, Int))
len2State s = convert (len2 s)
如果我们想要一个改变状态的函数呢? 显然,我们不能用convert
来构建一个,因为我们写这个来通过状态。 让我们保持简单,写一个函数来用新值覆盖状态。 它需要什么类型的? 它需要一个Int
作为新的状态值,当然必须返回一个函数stateData -> (result, stateData)
,因为这是我们的状态包装器所需要的。 覆盖状态值在状态计算之外并没有真正的result
值,所以我们的结果只是()
,它是在Haskell中表示“无值”的零元素元组。
overwriteState :: Int -> (Int -> ((), Int))
overwriteState newState _ = ((), newState)
那很简单! 现在,让我们用这些状态数据做一些事情。 让我们从上面将len2State
重len2State
更明智的东西:我们将比较字符串长度和当前状态值。
lenState :: String -> (Int -> (Bool, Int))
lenState s i = ((length s) == i, i)
我们可以将它推广到一个转换器和一个无状态函数中,就像我们之前做的那样? 不太容易。 我们的len
功能将需要以国家作为参数,但我们不希望它“了解”状态。 的确很尴尬。 但是,我们可以编写一个快速帮助函数来处理我们所有的事情:我们将给它一个需要使用状态值的函数,并且它将传递值并将所有内容打包回状态函数离开len
毫无收获。
useState :: (Int -> Bool) -> Int -> (Bool, Int)
useState f d = (f d, d)
len :: String -> Int -> Bool
len s i = (length s) == i
lenState :: String -> (Int -> (Bool, Int))
lenState s = useState (len s)
现在,棘手的部分 - 如果我们想将这些函数串在一起呢? 比方说,我们希望对字符串使用lenState
,如果结果为false,则将状态值加倍,然后再次检查字符串,如果两次检查都返回true。 我们拥有完成这项任务所需的所有部分,但全部写出来将是一件痛苦的事情。 我们可以创建一个函数来自动链接两个函数,每个函数返回类似状态的函数吗? 当然可以! 我们只需要确定它有两个参数:第一个函数返回的状态函数,以及一个将前一个函数的result
类型作为参数的函数。 让我们看看结果如何:
chainStates :: (Int -> (result1, Int)) -> (result1 -> (Int -> (result2, Int))) -> (Int -> (result2, Int))
chainStates prev f d = let (r, d') = prev d
in f r d'
所有这些都是将第一个状态函数应用于某些状态数据,然后将第二个函数应用于结果和修改的状态数据。 很简单,对吧?
现在,有趣的部分是:在chainStates
和convert
,我们几乎应该能够将无状态函数的任意组合转换为启用状态的函数! 我们现在唯一需要的是替换useState
,它返回状态数据作为其结果,以便chainStates可以将它传递给函数,这些函数不知道我们正在使用的技巧。 另外,我们将使用lambdas接受前面函数的结果并给它们临时名称。 好的,让我们做到这一点:
extractState :: Int -> (Int, Int)
extractState d = (d, d)
chained :: String -> (Int -> (Bool, Int))
chained str = chainStates extractState $ state1 ->
let check1 = (len str state1) in
chainStates (overwriteState (
if check1
then state1
else state1 * 2)) $ _ ->
chainStates extractState $ state2 ->
let check2 = (len str state2) in
convert (check1 || check2)
并尝试一下:
> chained "abcd" 2
(True, 4)
> chained "abcd" 3
(False, 6)
> chained "abcd" 4
(True, 4)
> chained "abcdef" 5
(False, 10)
当然,我们不能忘记,国家实际上是一个包装类似国家职能的单子,让我们远离它们,所以我们建立的漂亮功能都不会帮助我们实现真正的目标。 或者他们会? 令人震惊的是,真正的国家monad以不同的名字提供了所有相同的功能:
runState (State s) = s
return r = State (convert r)
(>>=) s f = State (d -> let (r, d') = (runState s) d in
runState (f r) d')
get = State extractState
put d = State (overwriteState d)
请注意>> =与chainStates几乎相同,但没有好的方法可以使用chainStates来定义它。 所以,为了包装起来,我们可以用真实状态重写最后一个例子:
chained str = get >>= state1 ->
let check1 = (len str state1) in
put (if check1
then state1 else state1 * 2) >>= _ ->
get >>= state2 ->
let check2 = (len str state2) in
return (check1 || check2)
或者,所有人都用同样的标记来表达:
chained str = do
state1 <- get
let check1 = len str state1
_ <- put (if check1 then state1 else state1 * 2)
state2 <- get
let check2 = (len str state2)
return (check1 || check2)
首先,你的例子过于复杂,因为它不需要在状态monad中存储val
; 只有种子是持久状态。 其次,我认为如果不是使用标准状态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是一个一次性的机械程序。 尽我所能,你的代码应该像这样解析(回到你原来的类型和代码,我不同意):
nextVal = get >>= Random seed val ->
let seed' = updateSeed seed
val' = even seed'
in put (Random seed' val') >>= _ -> return val'
为了使嵌套结构更清晰一些,我对缩进采取了很大的自由度。
你有一些很好的回应。 我在与国家monad合作时所做的是在我的脑海中用s -> (s,a)
取代State sa
(毕竟,这实际上就是这样)。
然后你得到一个类似于bind的类型:
(>>=) :: (s -> (s,a)) ->
(a -> s -> (s,b)) ->
(s -> (s,b))
你会发现绑定只是一种特殊的函数组合运算符,如(.)
我在这里写了关于州monad的博客/教程。 这可能不是特别好,但通过写作让我更好一些。
链接地址: http://www.djcxy.com/p/42911.html上一篇: State Monad, sequences of random numbers and monadic code