簡體   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