[英]Converting a (List of Future of Either) into a (Future of Either of List) in Scala
我有一个宠物Scala项目的情况,我真的不知道如何克服。
以下示例显示了我的问题。
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
???
}
我希望createLists
返回Future[Either[ErrorCreatingList, List[MyList]]]
但我不知道怎么做,因为listsWithId
的类型为List[Future[scala.Either[ErrorCreatingList, MyList]]]
,这使得感。
有办法吗? 一位朋友告诉我“这就是Cats的用途”,但这是唯一的选择,我的意思是,我不能只使用Scala核心库中的内容吗?
谢谢。
在List[Future[???]]
上使用Future.sequence
来创建Future[List[???]]
val listOfFuture: List[Future[???]] = ???
val futureList: Future[List[???]] = Future.sequence(listOfFuture)
以下是使用Cats的方法:
listFutureEither.traverse(EitherT(_)).value
以下是人们如何能够快速看到“scala-cats中必须有这样的东西”:
Future
是一个单子 Future[Either[E, ?]]
EitherT[E, Future, ?]
Future[Either[E, ?]]
基本上是EitherT[E, Future, ?]
,所以它也是一个monad Monad
都自动成为一个Applicative
M[X] = EitherT[E, Future, X]
是一个Applicative
A
和可遍历的T
,将T[A[X]]
交换为A[T[X]]
是微不足道的。 List
有一个Traverse
实例 Traverse[List]
从List[EitherT[E, Future, X]]
到EitherT[E, Future, List[X]]
Future[Either[E, List[X]]]
是微不足道的Future[Either[E, List[X]]]
将这个逐步解释转换为代码产生:
// 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
将map
+ sequence
融合到traverse
并清理一些不必要的类型参数会导致上面更短的解决方案。
因此, val eithers = Future.traverse(myLists)(createList)
将为您提供Future[List[Either[ErrorCreatingList, MyList]]]
。
您现在可以将其转换为您想要的,但这取决于您希望如何处理错误。 如果某些请求返回错误,其他请求成功,会发生什么?
此示例返回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)
}
我不是cats
专家,但我认为它唯一.right
就是不必在.map
之前输入.right
...但scala 2.12默认也是这样做的。
还有另一个名为scalactic
库,它添加了一些有趣的功能,让你可以将多个错误结合在一起......但是你必须在右边有错误才能工作......这几乎与其他所有东西都不相容。 如果必须的话,“手动”组合这些错误并不难,我只是说这样做而不是转换为scalactic,除了不相容之外,它还有相当大的学习曲线并且会损害可读性。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.