以功能方式进行多个API调用

Enr*_*ina 6 functional-programming scala category-theory scala-cats

通过使用Scala和Cats(或者可能是另一个专注于类别理论和/或函数式编程的库),以最具功能性(代数)的方式解决这个问题的最佳方法是什么?

资源

如果我们有以下方法执行REST API调用来检索单个信息?

type FutureApiCallResult[A] = Future[Either[String, Option[A]]]

def getNameApiCall(id: Int): FutureApiCallResult[String]
def getAgeApiCall(id: Int): FutureApiCallResult[Int]
def getEmailApiCall(id: Int): FutureApiCallResult[String]
Run Code Online (Sandbox Code Playgroud)

如您所见,它们会产生异步结果.Either monad用于在API调用期间返回可能的错误,而Option用于在API未找到资源时返回None(这种情况不是错误,而是可能的和期望的结果类型).

以功能方式实现的方法

case class Person(name: String, age: Int, email: String)

def getPerson(id: Int): Future[Option[Person]] = ???
Run Code Online (Sandbox Code Playgroud)

如果任何API调用失败或任何API调用返回None(整个Person实体无法组合),则此方法应使用上面定义的三个API调用方法异步组合并返回Person或None.

要求

出于性能原因,所有API调用必须以并行方式完成

我猜

我认为最好的选择是使用Cats Semigroupal Validated但是在尝试处理Future和如此多的嵌套Monads时我迷路了:S

任何人都可以告诉我你将如何实现这一点(即使改变方法签名或主要概念)或指向我正确的资源?我在编码时对Cats和Algebra很新,但我想学习如何处理这种情况,以便我可以在工作中使用它.

Bar*_*ski 18

这里的关键要求是它必须并行完成.这意味着使用monad的明显解决方案是out,因为monadic bind是阻塞的(它需要结果以防它必须在它上分支).所以最好的选择是使用applicative.

我不是Scala程序员,因此我无法向您展示代码,但我的想法是应用程序仿函数可以提升多个参数的函数(常规函子提升单个参数的函数使用map).在这里,你可以使用类似的东西map3解除三个参数的构造函数Person来处理三个FutureResults.搜索"Scala中的应用未来"会有一些点击.也有对应用性的实例EitherOption和,不像单子,applicatives可以一起轻松组成.希望这可以帮助.

  • 不记得Cats,但在Scalaz中有一个方便的ApplicativeBuilder实例可用于applicative functors,它是通过`| @ |`符号构造的,它需要一个函数作为参数应用.对于任意数量的参数,它都是`map2`的通用版本.代码最终看起来像这样:`(getName | @ | getAge | @ | getEmail)((姓名,年龄,电子邮件)=>人(姓名,年龄,电子邮件)) (2认同)

Luk*_*itz 8

您可以使用cats.Parallel类型类.这使得一些非常整齐的组合器EitherT能够在并行运行时累积误差.因此,最简单,最简洁的解决方案是:

type FutureResult[A] = EitherT[Future, NonEmptyList[String], Option[A]]

def getPerson(id: Int): FutureResult[Person] = 
  (getNameApiCall(id), getAgeApiCall(id), getEmailApiCall(id))
    .parMapN((name, age, email) => (name, age, email).mapN(Person))
Run Code Online (Sandbox Code Playgroud)

有关Parallel访问cats文档的更多信息.

编辑:这是没有内在的另一种方式Option:

type FutureResult[A] = EitherT[Future, NonEmptyList[String], A]

def getPerson(id: Int): FutureResult[Person] = 
  (getNameApiCall(id), getAgeApiCall(id), getEmailApiCall(id))
    .parMapN(Person)
Run Code Online (Sandbox Code Playgroud)