[英]Haskell: Join on State Monad
如何正式计算/解释以下表达式?
runState (join (State $ \s -> (push 10,1:2:s))) [0,0,0]
我理解非正式的解释,它说:首先运行外部有状态计算,然后运行结果计算。
好吧,这对我来说很奇怪,因为如果我遵循join
和>>=
定义,在我看来我必须从内部monad( push 10
)作为id
的参数开始,然后执行... hmmmm ...好吧...我不确定...为了得到应该是什么结果:
((),[10,1,2,0,0,0])
但是如何用正式定义来解释它:
instance Monad (State s) where
return x = State $ \s -> (x,s)
(State h) >>= f = State $ \s -> let (a, newState) = h s
(State g) = f a
in g newState
和
join :: Monad m => m (m a) -> m a
join n = n >>= id
同样,由于有一些“直觉” /视觉含义(与仅满足蒙纳德法律的形式化定义相反),很难理解国家Monad的绑定( >>=
)的定义。 它具有不太正式和更直观的含义吗?
如果你擅长的类型, join
的State s
您可以:
join :: State s (State s a) -> State s a
因此,如果给定一个有状态计算并返回另一个有状态计算的结果,则join
将它们合并为一个。
在您的问题中未提供push
的定义,但我认为它看起来像:
push :: a -> State [a] ()
push x = modify (x:)
以及某些State
类型
data State s a = State (s -> (a, s))
State sa
的值是一个函数,给定类型为s
的当前状态的值,该函数将返回一个包含类型a
a的结果和新状态值的对。 因此
State $ \s -> (push 10,1:2:s)
具有类型State [Int] (State [Int] ())
(或Int
以外的其他一些数字类型)。外部State
函数作为结果返回另一个State
计算,并更新状态以将值1
和2
推入状态。
此State
类型的join
实现如下所示:
join :: State s (State s a) -> State s a
join outer = State $ \s ->
let (inner, s') = runState outer s
in runState inner s'
因此它构造了一个新的有状态计算,该状态计算首先运行外部计算以返回包含内部计算和新状态的对。 然后以中间状态运行内部计算。
如果将示例插入此定义,则
outer = (State $ \s -> (push 10,1:2:s))
s = [0,0,0]
inner = push 10
s' = [1,2,0,0,0]
因此结果是runState (push 10) [1,2,0,0,0]
,即((),[10,1,2,0,0,0])
State
的经典定义非常简单。
newtype State s a = State {runState :: s -> (a,s) }
State sa
是一个“计算”(实际上只是一个函数),它采用s
类型s
某种东西(初始状态),并产生a
类型a
某种东西(结果)和s
类型s
某种东西(最终状态)。
您在问题中为>>=
给出的定义使State sa
成为“惰性状态转换器”。 这对于某些事情很有用,但是比严格的版本(它看起来像这样)更难理解,行为也不好:
m >>= f = State $ \s ->
case runState m s of
(x, s') -> runState (f x) s'
我消除了懒惰,还利用机会使用了记录选择器,而不是对State
模式匹配。
这是在说什么 给定初始状态,我runState ms
状态runState ms
以获得结果x
和新状态s'
。 我将f
应用于x
以获得状态转换器,然后以初始状态s'
运行该转换器。
惰性版本仅在元组上使用惰性模式匹配。 这意味着函数f
可以尝试生成状态转换器而无需检查其参数,并且转换器可以尝试运行而无需查看初始状态。 在某些情况下,您可以使用这种懒惰来打结递归结,实现有趣的功能(例如mapAccumR
,并在惰性增量流处理中使用状态,但是大多数时候您并不是真的想要/不需要它。
我认为Lee很好地解释了join
作用。
您提到了join
和>>=
的定义,因此,让我们尝试一下。
runState (join (State $ \s -> (push 10,1:2:s))) [0,0,0] = ?
定义又是
instance Monad (State s) where
-- return :: a -> State s a
return x = State $ \s -> (x,s)
所以对于x :: a
, State $ \\s -> (x,s) :: State sa
; (*)---->
(State h) >>= f = State $ \s -> let (a, newState) = h s
(State g) = f a
in g newState
join m = m >>= id
和runState :: State sa -> s -> (a, s)
,即它应该是(*)<----
runState (State g) s = gs
。 因此,遵循定义
runState (join (State $ \s -> (push 10,1:2:s))) [0,0,0]
= runState (State g) [0,0,0]
where (State g) = join (State $ \s -> (push 10,1:2:s))
= (State $ \s -> (push 10,1:2:s)) >>= id
-- (State h ) >>= f
= State $ \s -> let (a, newState) = h s
(State g) = id a
h s = (push 10,1:2:s)
in g newState
= State $ \s -> let (a, newState) = (push 10,1:2:s)
(State g) = a
in g newState
= State $ \s -> let (State g) = push 10
in g (1:2:s)
现在, push 10 :: State sa
应该与State g
匹配,其中g :: s -> (a, s)
; 它最有可能被定义为push 10 = State \\s-> ((),(10:) s)
; 所以我们有
= State $ \s -> let (State g) = State \s-> ((),(10:) s)
in g (1:2:s)
= State $ \s -> let g s = ((),(10:) s)
in g (1:2:s)
= State $ \s -> ((),(10:) (1:2:s))
= runState (State $ \s -> ((),(10:) (1:2:s)) ) [0,0,0]
= (\s -> ((),(10:) (1:2:s))) [0,0,0]
= ((), 10:1:2:[0,0,0])
。 因此,您可以看到push 10
首先是作为结果值生成的( (a, newState) = (push 10,1:2:s)
); 然后将其视为State sa
类型的计算描述,因此最后运行(不是您想的那样首先运行)。
如Lee所述, join :: State s (State sa) -> State sa
; 这种类型的含义是, State s (State sa)
是将State sa
作为其结果值的计算,即push 10
; 只有掌握了它,我们才能运行它。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.