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 - 我无法理解putget函数究竟做了什么。 我可以猜测他们“打包”和“解压缩”状态。 但do-sugar背后的机制仍然难以捉摸。

那么,关于这个代码的其他一般评论是非常受欢迎的。 我有时会跟Haskell一起合作,我可以创建一个可以工作的代码,并且做我期望的代码,但是我不能“跟随评估”,因为我习惯于使用命令式程序。


国家monad起初看起来有点混乱, 让我们按诺曼拉姆齐的建议做,并从头开始实施。 警告,这很长!

首先,State有两个类型参数:包含的状态数据的类型和最终计算结果的类型。 这里我们将分别使用stateDataresult作为它们的类型变量。 如果你仔细想想,这是有道理的。 基于状态的计算的定义特征是它在产生输出的同时修改状态。

不太明显的是,类型构造函数从一个状态到一个修改后的状态和结果,如下所示:

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类型是Intresult类型是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)

那很简单! 现在,让我们用这些状态数据做一些事情。 让我们从上面将len2Statelen2State更明智的东西:我们将比较字符串长度和当前状态值。

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'

所有这些都是将第一个状态函数应用于某些状态数据,然后将第二个函数应用于结果和修改的状态数据。 很简单,对吧?

现在,有趣的部分是:在chainStatesconvert ,我们几乎应该能够将无状态函数的任意组合转换为启用状态的函数! 我们现在唯一需要的是替换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

下一篇: How much memory is my windows app really using?