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