Maybe 和 Either 单子,short-circuiting,以及性能

Maybe and Either monads, short-circuiting, and performance

Functional Programming in C++,在第 214 页,参考与 Haskell 的 Either 相同的 expected<T,E> 单子,读取

[...] as soon as any of the functions you're binding to returns an error, the execution will stop and return that error to the caller.

然后,在下面的标题中,它显示为

If you call mbind [equivalent to Haskell's >>=] on an expected that contains an error,, mbind won't even invoke the transformation function; it will just forward that error to the result.

这似乎是在“调整”之前写的内容。 (我很确定 LYAH or RWH 在某个地方强调没有 short-circuiting;如果你记得在哪里,请提醒我。)

确实,根据 Haskell,我的理解是,在单子绑定链中,所有绑定都是真实发生的;然后他们如何处理作为第二个参数传递给他们的函数,取决于特定的 monad。

MaybeEither 的情况下,当绑定传递 NothingLeft x 参数时,第二个参数将被忽略。

不过,在这两种特定情况下,我想知道这样做是否会降低性能

justPlus1 = Just . (+1)
turnToNothing = const Nothing
Just 3 >>= turnToNothing >>= justPlus1
                         >>= justPlus1
                         >>= justPlus1
                         >>= justPlus1
                         >>= justPlus1

因为在这些情况下链不能真正做任何事情,因为

Nothing >>= _ = Nothing
Left l >>= _ = Left l

考虑以下表达式:

result :: Maybe Int
result = x >>= f >>= g >>= h

在那个表达式中,当然,x :: Maybe a对于某些a,而fgh中的每一个都是函数,具有h 返回 Maybe Int 但管道的中间类型可以是任何包裹在 Maybe 中的东西。也许 f :: String -> Maybe String, g :: String -> Maybe Char, h :: Char -> Maybe Int.

让我们也明确关联性:

result :: Maybe Int
result = ((x >>= f) >>= g) >>= h

要计算表达式,实际上必须调用每个 bind (>>=),但不一定调用函数 fg,或h。最终绑定到 h 需要检查它的 left-hand 参数来决定它是 Nothing 还是 Just something;为了确定我们需要调用对 g 的绑定,并决定我们需要调用对 f 的绑定,这必须至少查看 x。但是一旦这些绑定中的任何一个产生 Nothing,我们只需为每一步检查 Nothing 付费(非常便宜),而不是为调用(可能昂贵的)下游函数付费。

假设x = Nothing。然后 f 的绑定会检查它,看到 Nothing,并且根本不会调用 f。但是我们仍然需要绑定它的结果,以便知道它是否是 Nothing。这继续沿着链条向下直到最后我们得到 result = Nothing,调用了 >>= 三次但是 none 函数 fg 或 [=16] =].

EitherLeft 值的行为相似,其他 monad 可能有不同的行为。列表可以调用每个函数一次、多次或不调用;元组 monad 只调用每个函数一次,没有 short-circuiting 或其他多重性特征。

您似乎对这些类型的 Monad 实例在 Haskell 中的工作方式存在误解。你说:

Indeed, my understanding, from Haskell, is that in a chain of monadic functions, all of the functions are called,

但事实显然并非如此。事实上任何时候你计算

Nothing >>= f

其中 fa -> Maybe b 类型的任何函数,然后根据 Maybe monad 的 >>= 的实现进行计算,即:

Just x >>= f = f x
Nothing >>= f = Nothing

所以 f 确实会在 Just 的情况下被调用,但不会在 Nothing 的情况下被调用。所以我们看到确实存在“短路”。事实上,由于 Haskell 是惰性的,默认情况下每个函数都“短路”——除非需要产生结果,否则不会计算任何东西。

这是一个关于性能的有趣问题 - 而不是我个人知道如何回答的问题。当然,正如我刚刚解释的那样,一旦遇到 Nothing,链中的以下函数的 none 将被评估 - 但执行模式匹配以查看它不太可能是免费的。或许编译器能够通过推断它可以在遇到 Nothing 时放弃整个计算来优化它。但我不确定。