如果我们有以下代码:
import Control.Monad.State
type Stack = [Int]
pop :: State Stack Int
pop = state $ \(x:xs) -> (x,xs)
push :: Int -> State Stack ()
push a = state $ \xs -> ((),a:xs)
stackManip :: State Stack Int
stackManip = do
push 3
x <- pop
pop
我们运行的是:
command: runState stackManip [1]
result: (1,[])
&#39; Haskell&#39;知道给我们的&#39; x&#39;价值3? 那么,他怎么知道从状态monad中拿出3而不是状态(在这种情况下是堆栈)?
与Maybe monad相同的问题:
do
x <- Just 5
这里只有一个值,但Haskell如何知道采用&#39; 5&#39;并将其交给&#39; x&#39;?
答案 0 :(得分:3)
这归结为>>=
的定义。您应该注意do
---这里使用的功能---只是调用>>=
的瘦语法糖:
do
x <- pop
pop
= pop >>= \ x -> pop
和(忽略newtype包装器)>>=
被定义为State
:
a >>= f = \ s -> case a s of
(x, s') -> f x s'
因此f
将结果作为其第一个参数(名为x
),将状态作为其(静默)第二个参数(pop
定义中的一个)。
请注意,您无法反过来定义>>=
:
a >>= f = \ s -> case a s of
(x, s') -> f s' x
因为它会有类型
State s a -> (s -> State a b) -> State a b
通过类型分配了错误的类型变量。 (这就是为什么Haskell更喜欢单字母类型变量:哪种类型的变量与它们实际意义相同的重要性。)
答案 1 :(得分:1)
x <- pop
行大致从x
获取pop
。 pop
操作返回3
,而不是整个堆栈。您还可以从类型中看到:
pop :: State Stack Int -- returns Int, keeps Stack as state
相比之下,get
的类型为:
get :: State Stack Stack -- returns Stack, keeps Stack as state
所以,x <- get
将得到整个州的堆栈。
如果您想要更详细的解释,我建议您查看(>>=)
monad的State Stack
定义方式。然后,在将其作为
push 3 >>= ( \ _ ->
pop >>= ( \ x ->
pop ))
经过多次简化后,您应该达到以下类似的目的:
State (\stack0 -> let (_, stack1) = unState (push 3) stack0
(x, stack2) = unState pop stack1
(_, stack3) = unState pop stack2
in stack3 )
然后,如果您愿意,可以内联push
和pop
并继续简化。但是你已经在上面看到了x
的来源。