[英]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 Monad
is automatically an Applicative
Monad
都自动成为一个Applicative
M[X] = EitherT[E, Future, X]
is an Applicative
M[X] = EitherT[E, Future, X]
是一个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
实例 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]]
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.