C++ 中的函数式编程,第 214 页,参考与 Haskell 的
expected<T,E>
相同的 Either
monad,内容为
[...] 一旦您绑定的任何函数返回错误,执行就会停止并将该错误返回给调用者。
然后,在下面的标题中,写着
如果您在包含错误的
上调用mbind
[相当于 Haskell 的>>=
],expected
甚至不会调用转换函数; 它只会将该错误转发给结果。mbind
这似乎“调整”了之前写的内容。 (我很确定LYAH或RWH在某个地方强调了没有短路;如果你记得在哪里,请提醒我。)
事实上,我对 Haskell 的理解是,在一元绑定链中,所有绑定都是真实发生的;然后他们如何处理作为第二个参数传递给他们的函数,取决于特定的 monad。
对于
Maybe
和 Either
,当绑定传递 Nothing
或 Left 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
,并且每个f
、g
和h
都是函数,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 (
>>=
),但不一定调用函数 f
、g
或 h
。最终,对 h
的绑定需要检查其左侧参数来决定它是 Nothing
还是 Just something
;为了确定我们需要调用g
的绑定,并决定我们需要调用f
的绑定,这至少必须查看x
。但是,一旦这些绑定中的任何一个产生 Nothing
,我们只需在每一步检查 Nothing
付费(非常便宜),而不是调用(可能昂贵)下游函数。
假设
x = Nothing
。然后对 f
的绑定会检查它,看到 Nothing
,并且根本不费心去调用 f
。但我们仍然需要绑定它的结果才能知道它是否是Nothing
。这样继续沿着链向下,直到最后我们得到 result = Nothing
,调用了 >>=
三次,但没有任何函数 f
、g
或 h
。
Either
与 Left
值的行为类似,其他 monad 可能有不同的行为。列表可以调用每个函数一次、多次或不调用;元组 monad 只调用每个函数一次,没有短路或其他多重特征。
您似乎误解了这些类型的 Monad 实例在 Haskell 中的工作方式。你说:
事实上,我对 Haskell 的理解是,在一元函数链中,所有函数都被调用,
但事实显然并非如此。事实上,任何时候你计算
Nothing >>= f
其中
f
是任何a -> Maybe b
类型的函数,那么它是根据>>=
对于Maybe
单子的实现来计算的,即:
Just x >>= f = f x
Nothing >>= f = Nothing
所以
f
确实会在 Just
情况下被调用,但在 Nothing
情况下不会被调用。所以我们看到确实存在“短路”。事实上,由于 Haskell 很懒,每个函数默认都会“短路”——除非需要产生结果,否则不会计算任何内容。
这是一个关于性能的有趣问题,但我个人不知道如何回答。当然,正如我刚才所解释的,一旦遇到
Nothing
,链中的以下函数都不会被评估 - 但执行模式匹配来查看这一点不太可能是免费的。也许编译器能够对此进行优化,因为一旦遇到 Nothing
,它就可以放弃整个计算。但我不确定。