理解 StateMonad

understanding StateMonad

我是一个 haskell 初学者,正在尝试理解 StateMonad 的定义,特别是绑定操作。摘自 Generalising Monads to Arrows 第 4 页。

instance Monad (StateMonad s) where
     return a = SM (\s -> (a, s))
     x >>= f = SM (\s -> let
                             SM x' = x
                             (a, s') = x' s
                             SM f' = f a
                             (b, s'') = f' s'
                         in (b, s''))

首先你需要了解>>=的类型;我假设你这样做了,因为它在那篇论文的第 2 页上,你已经过了。

如果我们定义runState,bind 的定义可能更容易理解。

newtype SM s a = SM (s -> (a, s))

runState ::  SM a  -> s -> (a, s)
runState    (SM f)    s =  f s
-- this is equivalent to
-- runState (SM f) =  f

runState 运行 通过提取转换状态的函数 f 并将其应用于初始状态 s 来创建状态 monad。函数 f returns 类型 (a, s) 的元组。该元组包含依赖于状态的值(a 类型)和一个新状态(s 类型)。以下是等价的

let (a, s') = runState x s
in ...

let SM x' = x
    (a, s') = x' s
in ...

这两个函数都从 x 中提取了状态如何转换的函数 x',然后将其应用于初始状态 s。结果元组 (a, s') 包含状态相关值 a 和新状态 s'.

我们可以将>>=定义中的SM模式匹配替换为runState.

 x >>= f = SM (\s -> let
                         (a, s')  = runState x     s
                         (b, s'') = runState (f a) s'
                     in  (b, s''))

现在我们逐个分析。

Bind 构造一个新的 StateMonad,其函数依赖于某些初始状态 s。它 returns 一个依赖于状态的值 b 和一个新状态 s'':

 x >>= f = SM (\s -> let
                         ...
                     in  (b, s''))

状态相关值 a 和新状态 s' 是通过 运行 将状态 monad x 与初始状态 s 计算得出的:

                     let
                         (a, s')  = runState x     s

一个新的状态 monad f a 是根据用户提供的函数 f 和状态相关值 a 确定的。第二个状态 monad 是 运行,中间状态 s'。它计算另一个状态相关值 b 和最终状态 s''.

                         (b, s'') = runState (f a) s'   

第二个状态相关值 b 和最终状态 s'' 是为新 StateMonad.

构造的函数返回的值

一个略短的答案来补充 Cirdec 的...

首先,回想一下 x >>= f 中,x 是一个状态单子,f 是一个 fn,returns 是一个状态单子,x >>= f也是一个状态单子。

基本思路如下。给定一些当前状态 s(稍后我们 运行 由 x >>= f 表示的状态 monad 时将提供),我们首先想要 运行 xs,这将产生一些值 a,以及一个新状态 s'。然后我们想要 运行 f a,这给了我们另一个状态 monad。我们不只是想 运行 这个结果状态 monad f aany 状态;相反,我们希望 运行 它具有 x 产生的状态,即 s',给我们一些新值 b 和一些新状态 s''x >>= f 将表示来自 s -> (b, s'') 的计算。

记住:使用状态 monad 的全部意义在于避免显式地将状态作为另一个参数传递给我们所有的函数(那样会变得非常混乱,非常快)。所以这里的想法是,每当我们将动作绑定到状态 monad 时,让状态在幕后正确地线程化。

为此,我们 (1) 在 x 上进行模式匹配以获取内部的 fn,然后 (2) 运行 此函数与当前状态 s 获取结果 a 和一些新状态 s'。然后我们 (3) 计算 f a,其中 returns 是一个状态 monad,并在这个 monad 上进行模式匹配以获取内部的 fn;然后 (4) 运行 具有状态 s' 的 fn 得到一个值 b 和一些最终状态 s''。 (1) 到 (4) 对应于代码中紧跟在 let 之后的四行。你知道吗,我们刚刚定义了一个从 s(b, s'') 的 fn,它在幕后正确地传递状态。现在我们将把它包装在一个构造函数中,我们就完成了。 x >>= f 现在表示我们稍后可以通过提供初始状态 运行 进行计算。