【问题标题】:Why does Finatra use flatMap and not just map?为什么 Finatra 使用 flatMap 而不仅仅是地图?
【发布时间】:2021-03-08 01:02:11
【问题描述】:

这可能是一个非常愚蠢的问题,但我试图理解在 Finatra 的 HttpClient @987654321 中的此方法定义中使用 #flatMap 而不仅仅是 #map 背后的逻辑@:

def executeJson[T: Manifest](request: Request, expectedStatus: Status = Status.Ok): Future[T] = {
  execute(request) flatMap { httpResponse =>
    if (httpResponse.status != expectedStatus) {
      Future.exception(new HttpClientException(httpResponse.status, httpResponse.contentString))
    } else {
      Future(parseMessageBody[T](httpResponse, mapper.reader[T]))
        .transformException { e =>
          new HttpClientException(httpResponse.status, s"${e.getClass.getName} - ${e.getMessage}")
        }
    }
  }
}

当我可以只使用 #map 而拥有类似的东西时,为什么要创建一个新的 Future:

execute(request) map { httpResponse =>
  if (httpResponse.status != expectedStatus) {
    throw new HttpClientException(httpResponse.status, httpResponse.contentString)
  } else {
    try {
      FinatraObjectMapper.parseResponseBody[T](httpResponse, mapper.reader[T])
    } catch {
      case e => throw new HttpClientException(httpResponse.status, s"${e.getClass.getName} - ${e.getMessage}")
    }
  }
}

这是否纯粹是一种风格上的差异,在这种情况下使用 Future.exception 只是更好的风格,而抛出几乎看起来像是一种副作用(实际上不是,因为它不会退出 Future 的上下文)还是后面有什么东西,比如执行顺序之类的?

Tl;博士: 在 Future 中抛出与返回 Future.exception 有什么区别?

【问题讨论】:

  • 请注意,使用Future.exception 可能比抛出和捕获异常更快。 - 理想情况下,您永远不应该自己抛出异常,而应该在 Future 中使用 exception 之类的组合符来提升失败的值。
  • @LuisMiguelMejíaSuárez 谢谢,你有任何关于为什么它更快的指针/参考吗?
  • 因为抛出和捕获异常很慢,而仅返回一个值与重新调整任何其他值一样快。见:mattwarren.org/2016/12/20/Why-Exceptions-should-be-Exceptional
  • @LuisMiguelMejíaSuárez 啊,这是有道理的。因为 Future 必须捕获异常。感谢您的链接

标签: scala functional-programming flatmap finatra twitter-util


【解决方案1】:

从理论的角度来看,如果我们去掉异常部分(无论如何都无法推断出使用范畴论),那么只要您选择的构造(在您的情况下为 Twitter @987654321)这两个操作是完全相同的@) 形成一个有效的 monad。

我不想对这些概念进行长篇大论,所以我将直接介绍法律(使用 Scala Future):

import scala.concurrent.ExecutionContext.Implicits.global

// Functor identity law
Future(42).map(x => x) == Future(42)

// Monad left-identity law
val f = (x: Int) => Future(x)
Future(42).flatMap(f) == f(42) 

// combining those two, since every Monad is also a Functor, we get:
Future(42).map(x => x) == Future(42).flatMap(x => Future(x))

// and if we now generalise identity into any function:
Future(42).map(x => x + 20) == Future(42).flatMap(x => Future(x + 20))

是的,正如您已经暗示的那样,这两种方法是相同的。

但是,鉴于我们将例外情况纳入其中,我对此有三个 cmet:

  1. 要小心 - 在抛出异常时,Scala Future(也可能是 Twitter)故意违反左身份法,以换取一些额外的安全性。

例子:

import scala.concurrent.ExecutionContext.Implicits.global

def sneakyFuture = {
  throw new Exception("boom!")
  Future(42)
}

val f1 = Future(42).flatMap(_ => sneakyFuture)
// Future(Failure(java.lang.Exception: boom!))

val f2 = sneakyFuture
// Exception in thread "main" java.lang.Exception: boom!
  1. 正如@randbw 所提到的,抛出异常对于 FP 来说不是惯用的,它违反了函数纯度和值的引用透明性等原则。

Scala 和 Twitter Future 使您可以轻松地抛出异常 - 只要它发生在 Future 上下文中,异常就不会冒泡,而是导致 Future 失败。但是,这并不意味着应该允许在代码中随意乱扔它们,因为它会破坏程序的结构(类似于 GOTO 语句的执行方式,或循环中的 break 语句等)。

首选的做法是始终将每个代码路径评估为一个值,而不是到处乱扔炸弹,这就是为什么将 flatMap 映射到(失败的)Future 比映射到一些抛出炸弹的代码中更好的原因。

  1. 请注意引用透明度。

如果您使用map 而不是flatMap 并且有人从地图中获取代码并将其提取到函数中,那么如果此函数返回Future,您会更安全,否则有人可能会在外面运行它Future 上下文。

例子:

import scala.concurrent.ExecutionContext.Implicits.global

Future(42).map(x => {
  // this should be done inside a Future
  x + 1
})

这很好。但是在完全有效的重构之后(利用了引用透明的规则),你的代码变成了这样:

def f(x: Int) =  {
  // this should be done inside a Future
  x + 1
}
Future(42).map(x => f(x))

如果有人直接打电话给f,你会遇到问题。将代码包装成 Future 和 flatMap 会更安全。

当然,您可能会争辩说,即使使用flatMap,也有人可以从.flatMap(x => Future(f(x)) 中删除f,但可能性不大。另一方面,简单地将响应处理逻辑提取到一个单独的函数中,完全符合函数式编程将小函数组合成大函数的想法,而且很有可能会发生。

【讨论】:

  • 这是有道理的,虽然我不太确定 Twitter 是否在这里遵循 FP 原则(它们不在其他地方)。
  • 我正在从事的项目无论如何都不是纯粹的 FP,而且它在很多地方都会抛出异常(我有时希望我可以改变所有这些:D)所以有这个讨论我们应该更喜欢哪一个(抛出 vs. Future.exception),这就是我发布这个问题的原因。
【解决方案2】:

根据我对 FP 的理解,不会抛出异常。正如你所说,这将是一个副作用。相反,异常是在程序执行的某个时刻处理的值。

Cats(我相信其他库也一样)也采用了这种技术 (https://github.com/typelevel/cats/blob/master/core/src/main/scala/cats/ApplicativeError.scala)。

因此,flatMap 调用允许将异常包含在此处满足的 Future 中,并在程序执行的稍后时间点进行处理,其中也可能发生其他异常值处理。

【讨论】:

    猜你喜欢
    • 2017-01-09
    • 1970-01-01
    • 2018-02-08
    • 2012-08-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多