【问题标题】:Scala: how to understand the flatMap method of Try?Scala:如何理解 Try 的 flatMap 方法?
【发布时间】:2013-12-10 07:48:02
【问题描述】:

Success的flatMap方法是这样实现的:

  def flatMap[U](f: T => Try[U]): Try[U] =
    try f(value)
    catch {
      case NonFatal(e) => Failure(e)
    }

我有点理解这个方法的作用,它可以帮助我们避免编写大量捕获代码。

但是在什么意义上它与普通的 flatMap 相似呢?

一个普通的 flatMap 接受一个序列序列,并将所有元素放入一个大的“平面”序列中。

但是 Try 的 flatMap 方法并没有真正展平任何东西。

那么,如何理解 Try 的 flatMap 方法呢?

【问题讨论】:

  • 搜索 monads/monoids 模式。 flatMap 将原始 A[T] 转换为 A[U]。 Option、Try、Future 不需要转换/展平。参见关于monads的非常古老的文档
  • 方法mapflatMap 不是关于序列的,它们是关于单子的。有许多 monad 与任何类型的集合无关。 TryFuture 就是这些例子。 (虽然Try 在技术上严格来说不是一个单子,但它对于大多数用途来说已经足够接近了。)
  • 但是,我的理解是 flatMap 之所以这样称呼,是因为它确实对序列进行了展平。因此,在将其应用于非集合单子时,至少会出现一些认知失调。除了“这不是汤,先生,这是肉汤”之外,要求解释和回答是合理的(c.f foreach,它具有相同的不和谐)。
  • @Paul:扁平化隐喻也符合更严格的 Monad 包装计算定义:它将转换的嵌套结构转换为扁平结构。您也可以将其替换为“展开”,而不会失去太多意义。
  • 那么'foreach'呢? :) 说真的,这是有帮助的解释。谢谢。

标签: scala error-handling functional-programming try-catch monads


【解决方案1】:

不进入 monad,而不是从集合的角度来考虑它,您可以从结构的角度来考虑它(其中集合成为具有许多条目的结构)。

现在,看看Try.flatmap 的签名(来自您的帖子):

def flatMap[U](f: T => Try[U]): Try[U] 函数 f 在 Try[T] 的上下文中将 T 转换为 Try[U]。

相比之下,假设操作是'map',结果会是:

def badMap[U](f: T => Try[U]): Try[Try[U]]

如您所见,flatmap 将结果“展平”到 Try[T] 的上下文中并生成 Try[U] 而不是嵌套的 Try[Try[U]]

您可以将相同的“嵌套结构扁平化”概念应用于您提到的集合。

【讨论】:

  • 我已经担心接受的答案会是一些复杂的单子理论,但相反,这是我立即想到的简单明了的答案!
【解决方案2】:

您可以认为 Try[T] 类似于仅包含一个元素的集合(如 Option[T])。

当“序列的序列”是“只有一个序列”时,map 和 flatmap 几乎是相似的。 唯一的区别是函数的签名。

在这种情况下不需要展平。

【讨论】:

    【解决方案3】:

    我发现 Dan Spiewak 的“Monads Are Not Metaphors”对于让我了解单子非常有帮助。对于从 Scala 开始的人(比如我)来说,它比我发现的任何其他东西都更容易掌握——包括 Odersky 的著作。在阅读它时,请注意 'bind'=='flatMap'。

    【讨论】:

      【解决方案4】:

      正如您在A Tour of Scala: Sequence Comprehensions 中看到的那样: “在 scala 中,支持操作过滤器、映射和 flatMap(具有正确类型)的每个数据类型都可以用于序列理解。”事实上,这意味着你可以像 monad 一样威胁它。
      monad 的 flatMap 有这样的签名:

      def flatMap(f: A => M[B]): M[B]
      

      scala 中的所有集合都具有单子接口,因此您可以将窄范围内的单子操作视为对序列的操作。但这还不是全部。如果某些 monad 将它们视为集合,则更令人困惑而不是有用。通常,flatMap 通过将这个 monad 与一个操作组合在一起来应用 monad“内容”的转换,从而导致另一个相同类型的 monad 实例。 所以你至少可以从两个方面来看待 monad:

      • Monad 是某种集合(或装有东西的盒子),该集合的元素是“内容”。
      • Monad 是某种上下文,monad 的元素只是在该上下文中进行的一些计算。

      有时将 monad 视为集合更容易,有时将其视为上下文更容易。至少对我来说。事实上,这两种方法是可以互换的,即您可以将列表(集合)视为可能返回任意数量结果的不确定性计算。

      因此,在 Try 的情况下,将其视为执行上下文可能更容易,具有两种状态,成功和失败。如果您想编写几个尝试然后其中一个处于失败状态,那么整个上下文将变为失败(链已断开)。否则,您可以对该 Tries 的“内容”进行一些操作,并且上下文为 Success。

      【讨论】:

        【解决方案5】:

        一个普通的 flatMap 接受一个序列序列,并将所有元素放入一个大的“平面”序列中。

        轻微修正:

        一个普通的flatMap 接受一个序列(更一般的monad),有一个参数是一个将元素转换为序列(monad)的函数,并返回一个“平面”序列(monad)。

        出于比较目的,这里提到的血腥子步骤:)。 flatmap 方法迭代调用f(element) 的输入序列,但创建一个单一的新结果序列。 “扁平化”部分在每个函数参数应用程序之后应用,f(element) - 它对结果子序列进行嵌套迭代,产生单个结果序列中的每个条目。

        Success 的等价物,内部带有 value(更常见的是 monad):

        • flatmap 有一个参数,它是将Success 转换为Try = Success(value)Failure(exception) 的函数。应用f(value) 后,结果已经是Try。 “flatten”部分是一个微不足道的/空操作:迭代这个函数结果只会给出一个条目,因此Try/Success/Failure甚至不需要实现Iterable)。不包装Success/Failure 的其他层,因此返回“平面”Try

          即“扁平”部分意味着它不会级联 Success/Failure 包装器,就像序列的 flatmap 不会级联(值树)层次结构中的序列一样。

        • 这与map不同,它的参数是将Success转换为任意类型U的函数;应用f(value) 后,地图必须在value/exception 周围添加一层新的Success/Failure

        【讨论】:

          【解决方案6】:

          一个普通的 flatMap 采用一系列序列,并将所有元素放入一个大的“平面”序列中

          这里将单词序列替换为 monad 是公平的,因为这个操作不仅仅与集合有关,实际上集合也是 monad。将Try 视为可以包含SuccessFailure 的集合

          【讨论】:

          • 对于没有 monad 概念的人来说,这几乎无法解释任何事情。
          • 一个很好的理解 monads 的演示文稿typeclassopedia.bitbucket.org
          猜你喜欢
          • 2014-10-14
          • 2021-10-19
          • 2020-10-14
          • 1970-01-01
          • 2013-07-24
          • 1970-01-01
          • 2019-12-11
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多