37

Futures を返す関数が 2 つあります。for-yield 内包表記を使用して、最初の関数から変更された結果を別の関数にフィードしようとしています。

このアプローチは機能します:

  val schoolFuture = for {
    ud <- userStore.getUserDetails(user.userId)
    sid = ud.right.toOption.flatMap(_.schoolId)
    s <- schoolStore.getSchool(sid.get) if sid.isDefined
  } yield s

ただし、「if」が含まれていることに満足していません。代わりにマップを使用できるようにする必要があるようです。

しかし、地図を試してみると:

  val schoolFuture: Future[Option[School]] = for {
    ud <- userStore.getUserDetails(user.userId)
    sid = ud.right.toOption.flatMap(_.schoolId)
    s <- sid.map(schoolStore.getSchool(_))
  } yield s

コンパイル エラーが発生します。

[error]  found   : Option[scala.concurrent.Future[Option[School]]]
[error]  required: scala.concurrent.Future[Option[School]]
[error]         s <- sid.map(schoolStore.getSchool(_))

いくつかのバリエーションで遊んでみましたが、機能する魅力的なものは見つかりませんでした. 誰かがより良い理解を提案したり、私の2番目の例の何が問題なのかを説明したりできますか?

以下は、Scala 2.10 で最小限だが完全に実行可能な例です。

import concurrent.{Future, Promise}

case class User(userId: Int)
case class UserDetails(userId: Int, schoolId: Option[Int])
case class School(schoolId: Int, name: String)

trait Error

class UserStore {
  def getUserDetails(userId: Int): Future[Either[Error, UserDetails]] = Promise.successful(Right(UserDetails(1, Some(1)))).future
}

class SchoolStore {
  def getSchool(schoolId: Int): Future[Option[School]] = Promise.successful(Option(School(1, "Big School"))).future
}

object Demo {
  import concurrent.ExecutionContext.Implicits.global

  val userStore = new UserStore
  val schoolStore = new SchoolStore

  val user = User(1)

  val schoolFuture: Future[Option[School]] = for {
    ud <- userStore.getUserDetails(user.userId)
    sid = ud.right.toOption.flatMap(_.schoolId)
    s <- sid.map(schoolStore.getSchool(_))
  } yield s
}
4

5 に答える 5

22

(正しい答えを与えるために編集されました!)

ここで重要なのは、正しい署名がないため、内部で作成しないことFutureです。念のため、脱糖については次のようにします。Option forflatMap

for ( x0 <- c0; w1 = d1; x1 <- c1 if p1; ... ; xN <- cN) yield f
c0.flatMap{ x0 => 
  val w1 = d1
  c1.filter(x1 => p1).flatMap{ x1 =>
    ... cN.map(xN => f) ... 
  }
}

(ここで、任意のifステートメントがチェーンに a をスローしfilterます--私は 1 つの例を示しました--そして equals ステートメントは、チェーンの次の部分の前に変数を設定するだけです)。flatMapあなたは他の sしかできないので、最後のものを除くFutureすべてのステートメントc0, , ... は.c1Future

さて、getUserDetailsどちらgetSchoolも を生成FuturesしますがsidOptionであるため、 の右側に置くことはできません<-。残念ながら、これを行うためのすぐに使えるクリーンな方法はありません。オプションの場合oは、できます

o.map(Future.successful).getOrElse(Future.failed(new Exception))

Optionを完成済みのに変えFutureます。そう

for {
  ud <- userStore.getUserDetails(user.userId)  // RHS is a Future[Either[...]]
  sid = ud.right.toOption.flatMap(_.schoolId)  // RHS is an Option[Int]
  fid <- sid.map(Future.successful).getOrElse(Future.failed(new Exception))  // RHS is Future[Int]
  s <- schoolStore.getSchool(fid)
} yield s

トリックを行います。それはあなたが持っているものよりも優れていますか?疑わしい。しかし、もしあなたが

implicit class OptionIsFuture[A](val option: Option[A]) extends AnyVal {
  def future = option.map(Future.successful).getOrElse(Future.failed(new Exception))
}

その後、突然、for-comprehension が再び合理的に見えます。

for {
  ud <- userStore.getUserDetails(user.userId)
  sid <- ud.right.toOption.flatMap(_.schoolId).future
  s <- schoolStore.getSchool(sid)
} yield s

これは、このコードを記述する最良の方法ですか? おそらくそうではありません。Noneその時点で他に何をすべきかわからないという理由だけで、 a を例外に変換することに依存しています。の設計上の決定により、これを回避するのは困難ですFuture。元のコード (フィルターを呼び出す) は、少なくともそれを行うのと同じくらい良い方法であることをお勧めします。

于 2013-01-17T19:10:43.817 に答える
14

についての同様の質問に対するこの回答Promise[Option[A]]が役立つ場合があります。に置き換えるだけFutureですPromise

あなたの質問から、次のタイプgetUserDetailsを推測しています。getSchool

getUserDetails: UserID => Future[Either[??, UserDetails]]
getSchool: SchoolID => Future[Option[School]]

からの失敗値を無視Eitherし、代わりに に変換するため、Option事実上、タイプ の 2 つの値が得られますA => Future[Option[B]]

Monadインスタンスを取得したらFuturescalazにある場合もあれば、リンクした回答のように独自のインスタンスを作成することもできます)、OptionTトランスフォーマーを問題に適用すると、次のようになります。

for {
  ud  <- optionT(getUserDetails(user.userID) map (_.right.toOption))
  sid <- optionT(Future.successful(ud.schoolID))
  s   <- optionT(getSchool(sid))
} yield s

ud.schoolID型の互換性を保つために、(既に完了している) Future にラップされていることに注意してください。

この for-comprehension の結果は type になりOptionT[Future, SchoolID]ます。Future[Option[SchoolID]]トランスフォーマーのrunメソッドを使用して、型の値を抽出できます。

于 2013-01-17T19:33:01.820 に答える
8

が の場合、どのような動作をしたいですOption[School]None? 未来を失敗させたいですか?どのような例外がありますか?完了しないようにしますか? (それは悪い考えのように聞こえます)。

いずれにせよ、for 式のif節はメソッドの呼び出しを回避しfilterます。したがって、上の契約Future#filterは次のとおりです。

現在のフューチャに述語を満たす値が含まれている場合、新しいフューチャもその値を保持します。それ以外の場合、結果の Future は NoSuchElementException で失敗します。

ちょっと待って:

scala> None.get
java.util.NoSuchElementException: None.get

ご覧のとおり、None.get はまったく同じものを返します。

したがって、if sid.isDefinedshould を取り除くと、妥当な結果が返されます。

  val schoolFuture = for {
    ud <- userStore.getUserDetails(user.userId)
    sid = ud.right.toOption.flatMap(_.schoolId)
    s <- schoolStore.getSchool(sid.get)
  } yield s

の結果は のschoolFutureインスタンスになる可能性があることに注意してくださいscala.util.Failure[NoSuchElementException]。しかし、他にどのような動作が必要かについては説明していません。

于 2013-01-18T01:02:28.907 に答える
1

https://github.com/qifun/stateless-future使用するかhttps://github.com/scala/asyncA-Normal-Form変換する方が簡単 です。

于 2015-05-07T04:56:54.513 に答える