【问题标题】:Monads and do notationMonads 和 do 表示法
【发布时间】:2014-12-02 20:34:07
【问题描述】:

如果我们有以下代码:

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,[])

“Haskell”如何知道给我们的“x”赋值 3? 那么,他怎么知道从 State monad 中取出 3 而不是状态(在这种情况下是堆栈)?

与 Maybe monad 相同的问题:

do
 x <- Just 5 

这里只有一个值,但是 Haskell 怎么知道取 '5' 给 'x' 呢?

【问题讨论】:

    标签: haskell state monads


    【解决方案1】:

    这归结为&gt;&gt;= 的定义。您应该注意,do --- 对于此处使用的功能 --- 只是调用 &gt;&gt;= 的简单语法糖:

    do
       x <- pop
       pop
    = pop >>= \ x -> pop
    

    并且(忽略新类型包装器)&gt;&gt;=State 定义为:

    a >>= f = \ s -> case a s of
        (x, s') -> f x s'
    

    因此,f 将结果作为其第一个参数(名为 x 的参数)并将状态作为其(静默)第二个参数(pop 定义内的参数)。

    请注意,您不能反过来定义 &gt;&gt;=

    a >>= f = \ s -> case a s of
        (x, s') -> f s' x
    

    因为它会有类型

    State s a -> (s -> State a b) -> State a b
    

    其中类型变量通过类型的错误分布。 (这就是 Haskell 更喜欢单字母类型变量的原因:哪些类型变量相同比它们的实际含义重要得多。

    【讨论】:

    • 只是为了让这一点更清楚:do 表示法是语法糖,这使得重复使用&gt;&gt;=&gt;&gt; 更加清晰和漂亮。
    • @AJFarmar - 正确,虽然模式匹配版本 do (x:xn) &lt;- a; f x xn 要复杂得多。因此,我将我的评论限制在&lt;- 左侧有变量的版本。
    【解决方案2】:

    x &lt;- pop 行大致从pop 中提取xpop 操作返回 3,而不是整个堆栈。从类型上也可以看出:

    pop :: State Stack Int   -- returns Int, keeps Stack as state
    

    相比之下,get 的类型为:

    get :: State Stack Stack  -- returns Stack, keeps Stack as state
    

    所以,x &lt;- get 将获得整个状态堆栈。

    如果您想获得更详细的解释,我建议您查看(&gt;&gt;=) 是如何为State Stack monad 定义的。然后,在将其脱糖后将其应用于您的代码

    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 的来源。

    【讨论】:

    • pop :: State Stack Int -- 返回 Int,保持 Stack 状态 ||如果你说'returns Int',函数类型不会是这样的:State Stack -> Int 吗?我现在的阅读方式是 pop 函数返回带有 Stack 和 Int 的整个 State。
    • 您还应该注意,push 也是一个返回“带有 Stack 和 Int 的整个状态”的函数 do 表示法的吸引力之一是它隐藏了杂乱的细节并允许您专注于快乐的道路。
    • @Gert-Jan 请注意,State s a 被定义为data State s a = State (s -&gt; (a,s)),因此它是一个装在构造函数中的常规函数​​。此函数采用旧状态并产生结果(例如 Int)和新状态。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多