繁体   English   中英

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

[英]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.

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