Monads和记谱法

时间:2014-12-02 20:34:08

标签: haskell state monads

如果我们有以下代码:

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;?

2 个答案:

答案 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获取poppop操作返回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 )

然后,如果您愿意,可以内联pushpop并继续简化。但是你已经在上面看到了x的来源。