do 块内的执行顺序是否真的不取决于语句顺序?

Is it true that order of execution inside a do block doesn't depend on the statement order?

我正在阅读 https://wiki.haskell.org/Do_notation_considered_harmful 并惊讶地阅读了以下几行

Newcomers might think that the order of statements determines the order of execution. ... The order of statements is also not the criterion for the evaluation order.

wiki post 提供了一些示例来证明这一点 属性。虽然这些例子很有意义,但我仍然不完全相信这个说法是正确的,因为如果我写下类似

main = do
  putStrLn "foo"
  putStrLn "bar"
  putStrLn "baz"

三行是按照语句的顺序出来的。那么这里到底发生了什么?

它说的是语句的顺序不影响评估标准。正如@chi 在 IO monad 中指出的那样,效果是按顺序排列的,但它们的求值顺序仍然未知。一个 monad 的例子将使概念清晰:

test = do
  x <- Just (2 + undefined)
  y <- Nothing
  return (x + y)

在 ghci 中:

λ> test
Nothing

上面的代码有3个语句。可以脱糖成如下形式:

Just (2 + undefined) >>= \x -> Nothing >>= \y -> return (x + y)

现在由于 (>>=) 是左关联的,它将像这样计算:

(Just (2 + undefined) >>= \x -> Nothing) >>= \y -> return (x + y)

注意 Maybe monad 是这样定义的:

(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
Nothing  >>= _  =  Nothing    -- A failed computation returns Nothing
(Just x) >>= f  =  f x        -- Applies function f to value x

将值 (2 + undefined) 应用于函数 \x -> Nothing 将产生 Nothing。 表达式 2 + undefined 未计算,这要归功于后跟 Haskell.

的惰性计算策略

现在我们有一个简化的形式:

Nothing >>= \y -> return (2 + undefined + y)

查看它的 Monad 实例,您可以看到这将产生 Nothing,因为 Nothing >>= _ = Nothing。 如果参数是严格的怎么办:

test = do
  !x <- Just (2 + undefined)
  y <- Nothing
  return (y + x)

ghci 中的演示:

λ> test
*** Exception: Prelude.undefined

如果我们遵循严格的评估程序,那么您会发现顺序实际上很重要。但在惰性设置中,语句的顺序无关紧要。因此维基声称,"the order of statements is not the criterion for the evaluation order".