【问题标题】:Maybe and Either monads, short-circuiting, and performanceMaybe 和 Either 单子、短路和性能
【发布时间】:2021-01-04 16:21:48
【问题描述】:

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

[...] 只要您绑定到的任何函数返回错误,执行将停止并将该错误返回给调用者。

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

如果你在包含错误的expected 上调用mbind [相当于Haskell 的>>=],mbind 甚至不会调用转换函数; 它只会将该错误转发给结果

这似乎“调整”了之前写的内容。 (我很确定 LYAHRWH 在某处强调没有短路;如果您记得在哪里,请提醒我。)

确实,我从 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

【问题讨论】:

  • 我认为答案是您无需为Nothing 之后的内容支付任何费用,但您确实需要为在此之前展开堆栈付费,但我并不完全确定。在某些情况下,继续传递样式应该在某些方面更有效,例如解析器组合器库使用 CPS 而不是 Either 来处理错误。
  • @Hjulle,这个 CPS 东西也是单子吗?
  • @EnricoMariaDeAngelis:是的,在最一般的情况下,延续形成一个 monad/transformer Cont/ContT;如果你有一个现有的 monad 并且不想重写它以在内部使用 CPS 以避免代价高昂的左关联绑定,你可以使用 Codensity 自动完成(浅)。

标签: c++ performance haskell functional-programming monads


【解决方案1】:

考虑以下表达式:

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

当然,在那个表达式中,x :: Maybe a 代表一些 afgh 都是函数,h 返回 Maybe Int 但中间类型管道可以是任何包裹在Maybe 中的东西。也许是f :: String -> Maybe Stringg :: String -> Maybe Charh :: Char -> Maybe Int

让我们也明确关联性:

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

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

假设x = Nothing。然后绑定到f 进行检查,看到Nothing,并且根本不打扰调用f。但是我们仍然需要绑定它的结果才能知道它是否是Nothing。这会一直持续下去,直到最后我们得到result = Nothing,已经调用了三次>>=,但没有一个函数fgh

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

【讨论】:

  • 哦,所以就为什么链不能短路而言,原因是>>= 是关联的。毕竟,给定它的参数和返回值的类型,它只能是左关联的。
  • 此外,我知道你的这个答案一般适用于单子,不管是什么语言,只要一个仅限于纯函数。
  • 两面都支持。
  • @Enlico "关于为什么链不能短路,原因是>>= 是左关联的" true,但>=> 是右关联的,所以你只需要写x >>= (f >=> g >=> h) 获取实际的短路行为。此外,do 表示法转换为嵌套的 lambda 链,也嵌套在右侧。
【解决方案2】:

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

事实上,我对 Haskell 的理解是,在一个单子函数链中,所有的函数都会被调用,

但显然情况并非如此。确实在你计算的任何时候

Nothing >>= f

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

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

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

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

【讨论】:

  • 鉴于我自己的例子,很明显我严重误导了问题的那一部分,将一元函数与一元绑定混淆了,但感谢您指出这一点,我会尽快纠正.
  • 我改写得更恰当了。
猜你喜欢
  • 1970-01-01
  • 2015-11-30
  • 1970-01-01
  • 1970-01-01
  • 2017-11-23
  • 2012-09-09
  • 2011-12-25
  • 1970-01-01
  • 2021-08-06
相关资源
最近更新 更多