在Scala中将一个(List of Future of Any)转换为一个(Future of Ear of List



在一个备受宠爱的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猫中一定已经有这样的东西了":

  • Future是monad
  • 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]]]是微不足道的

将此逐步解释转化为代码产生:

// 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专家,但我认为它在这里唯一有帮助的是不必在最后的.map之前键入.right。。。但scala2.12默认情况下也会这样做。

还有另一个库,名为scalactic,它添加了一些有趣的功能,可以将多个错误组合在一起。。。但你必须在右边有错误才能奏效。。。这将与几乎所有其他内容都不兼容。如果必须的话,"手动"组合这些错误并不难,我想说,只是这样做,而不是切换到标量,标量除了不兼容之外,还有相当大的学习曲线,并损害可读性。

相关内容

最新更新