简体   繁体   English

在Scala中将(任一个的未来列表)转换为(列表中的任一个的未来)

[英]Converting a (List of Future of Either) into a (Future of Either of List) in Scala

I have a situation in a pet Scala project that I don't really know how to overcome. 我有一个宠物Scala项目的情况,我真的不知道如何克服。

The following example shows my problem. 以下示例显示了我的问题。

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

case class MyBoard(id: Option[Int], name: String)
case class MyList(id: Option[Int], name: String, boardId: Option[Int] = None)
case class ErrorCreatingList(error: String)

def createList(myList: MyList): Future[Either[ErrorCreatingList, MyList]] =
  Future {
    // Let's close our eyes and pretend I'm calling a service to create this list
    Right(myList)
  }

def createLists(myLists: List[MyList],
                myBoard: MyBoard): Future[Either[ErrorCreatingList, List[MyList]]] = {

  val listsWithId: List[Future[scala.Either[ErrorCreatingList, MyList]]] =
    myLists.map { myList =>
      createList(myList.copy(boardId = myBoard.id))
    }

  //  Meh, return type doesn't match
  ???
}

I wanted createLists to return Future[Either[ErrorCreatingList, List[MyList]]] but I don't know how to do it, because listsWithId has the type List[Future[scala.Either[ErrorCreatingList, MyList]]] , which makes sense. 我希望createLists返回Future[Either[ErrorCreatingList, List[MyList]]]但我不知道怎么做,因为listsWithId的类型为List[Future[scala.Either[ErrorCreatingList, MyList]]] ,这使得感。

Is there a way to do it? 有办法吗? A friend told me "and that's what Cats is for", but is it the only option, I mean, can't I do it using just what's in the Scala core library? 一位朋友告诉我“这就是Cats的用途”,但这是唯一的选择,我的意思是,我不能只使用Scala核心库中的内容吗?

Thanks. 谢谢。

Use Future.sequence on your List[Future[???]] to make Future[List[???]] List[Future[???]]上使用Future.sequence来创建Future[List[???]]

val listOfFuture: List[Future[???]] = ???

val futureList: Future[List[???]] = Future.sequence(listOfFuture)

Here is how you can do it with Cats: 以下是使用Cats的方法:

listFutureEither.traverse(EitherT(_)).value

Here is how one can quickly see that "there must be something like this in scala-cats already": 以下是人们如何能够快速看到“scala-cats中必须有这样的东西”:

  • Future is a monad Future是一个单子
  • Future[Either[E, ?]] is essentially EitherT[E, Future, ?] , therefore it's also a monad Future[Either[E, ?]] EitherT[E, Future, ?] Future[Either[E, ?]]基本上是EitherT[E, Future, ?] ,所以它也是一个monad
  • Every Monad is automatically an Applicative 每个Monad都自动成为一个Applicative
  • So, M[X] = EitherT[E, Future, X] is an Applicative 因此, M[X] = EitherT[E, Future, X]是一个Applicative
  • For every applicative A and traversable T , it is trivial to swap T[A[X]] into A[T[X]] . 对于每个应用A和可遍历的T ,将T[A[X]]交换为A[T[X]]是微不足道的。
  • List has a Traverse instance List有一个Traverse实例
  • you should be able to use Traverse[List] to get from List[EitherT[E, Future, X]] to EitherT[E, Future, List[X]] 你应该能够使用Traverse[List]List[EitherT[E, Future, X]]EitherT[E, Future, List[X]]
  • From there, it's trivial to get to Future[Either[E, List[X]]] 从那里,到达Future[Either[E, List[X]]]是微不足道的Future[Either[E, List[X]]]

Translating this step-by-step explanation into code yields: 将这个逐步解释转换为代码产生:

// lines starting with `@` are ammonite imports of dependencies,
// add it to SBT if you don't use ammonite
@ import $ivy.`org.typelevel::cats-core:1.1.0`
@ import cats._, cats.data._, cats.implicits._
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.Either

// your input
val listFutureEither: List[Future[Either[String, Int]]] = Nil 

// monad transformer stack appropriate for the problem
type M[X] = EitherT[Future, String, X]

// converting input into monad-transformer-stack
val listM = listFutureEither.map(EitherT[Future, String, Int](_))

// solving your problem
val mList = Traverse[List].sequence[M, Int](listM)

// removing all traces of the monad-transformer-stack
val futureEitherList: Future[Either[String, List[Int]]] = mList.value

Fusing map + sequence into traverse and cleaning up some unnecessary type parameters results in the much shorter solution above. map + sequence融合到traverse并清理一些不必要的类型参数会导致上面更短的解决方案。

So, val eithers = Future.traverse(myLists)(createList) will give you Future[List[Either[ErrorCreatingList, MyList]]] . 因此, val eithers = Future.traverse(myLists)(createList)将为您提供Future[List[Either[ErrorCreatingList, MyList]]]

You can now transform it to what you want, but that depends on how you want to deal with the errors. 您现在可以将其转换为您想要的,但这取决于您希望如何处理错误。 What happens if some requests returned an error, and others succeeded? 如果某些请求返回错误,其他请求成功,会发生什么?

This example returns Right[List[MyList]] if everything succeeded, and Left with the first error otherwise: 此示例返回Right[List[MyList]]如果一切成功,并Left与否则第一个错误:

type Result = Either[ErrorCreatingList, List[MyList]]
val result: Future[Result] = eithers.map { 
   _.foldLeft[Result](Right(Nil)) { 
     case (Right(list), Right(myList)) => Right(myList :: list)
     case (x @ Left(_), _) => x
     case (_, Left(x)) => Left(x)
  }.right.map(_.reverse)
}

I am not cats expert, but I think the only thing it helps with here is not having to type .right before .map at the end ... but scala 2.12 does that by default too. 我不是cats专家,但我认为它唯一.right就是不必在.map之前输入.right ...但scala 2.12默认也是这样做的。

There is another library, called scalactic , that adds some interesting features, letting you combine multiple errors together ... but you'd have to have errors on the right for that to work ... which would be incompatible with pretty much everything else. 还有另一个名为scalactic库,它添加了一些有趣的功能,让你可以将多个错误结合在一起......但是你必须在右边有错误才能工作......这几乎与其他所有东西都不相容。 It's not hard to combine those errors "manually" if you have to, I'd say just do that rather than switching to scalactic, which, besides being incompatible, has a considerable learning curve and hurts readability. 如果必须的话,“手动”组合这些错误并不难,我只是说这样做而不是转换为scalactic,除了不相容之外,它还有相当大的学习曲线并且会损害可读性。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM