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
}
この回答Promise[Option[A]]
に関する同様の質問に役立つかもしれません。 Future
をPromise
に置き換えてください。
getUserDetails
とgetSchool
の次の型を質問から推測しています:
getUserDetails: UserID => Future[Either[??, UserDetails]]
getSchool: SchoolID => Future[Option[School]]
Either
からの失敗値を無視して、代わりにOption
に変換するため、A => Future[Option[B]]
タイプの2つの値が事実上存在します。
Monad
のFuture
インスタンスを取得したら( scalaz にインスタンスが1つあるか、リンクした回答のように独自に作成できます)、次のように適用します。問題の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の結果の型はOptionT[Future, SchoolID]
になります。トランスフォーマーのrun
メソッドを使用して、タイプFuture[Option[SchoolID]]
の値を抽出できます。
(正しい答えを与えるために編集されました!)
ここで重要なのは、Future
およびOption
正しいfor
署名がないため、flatMap
内で構成しないでください。 。思い出してください、そういう人のために:
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
ステートメントはfilter
をチェーンにスローします-例を1つだけ示しましたが、equalsステートメントは、チェーンの次の部分の前に変数を設定するだけです)。 flatMap
他のFuture
sしか使用できないため、すべてのステートメントc0
、c1
、...を除いて、最後のステートメントはFuture
を生成する方がよいでしょう。
現在、getUserDetails
とgetSchool
はどちらもFutures
を生成しますが、sid
はOption
であるため、右側に配置できません-<-
の片側残念ながら、これを行うためのクリーンな方法はありません。 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 {
ud <- userStore.getUserDetails(user.userId)
sid <- ud.right.toOption.flatMap(_.schoolId).future
s <- schoolStore.getSchool(sid)
} yield s
これはこのコードを書くための最良の方法ですか?おそらくそうではありません。 None
を例外に変換することに依存しています。その時点で他に何をすべきかわからないからです。 Future
の設計上の決定のため、これを回避するのは困難です。元のコード(フィルターを呼び出す)は、少なくともそれを行うための優れた方法であることをお勧めします。
Option[School]
がNone
の場合、どのような動作をしたいですか?未来を失敗させませんか?どんな例外がありますか?決して完了させませんか? (それは悪い考えのように聞こえます)。
とにかく、for式のif
句はfilter
メソッドの呼び出しを求めます。したがって、Future#filter
の契約は次のようになります。
現在のフューチャーに述語を満たす値が含まれている場合、新しいフューチャーもその値を保持します。それ以外の場合、結果のフューチャーはNoSuchElementExceptionで失敗します。
ちょっと待って:
scala> None.get
Java.util.NoSuchElementException: None.get
ご覧のとおり、None.getはまったく同じものを返します。
したがって、if sid.isDefined
を取り除くことは機能し、これは妥当な結果を返すはずです。
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]
のインスタンスになる可能性があることに注意してください。しかし、あなたはあなたが望む他のどのような振る舞いを述べていません。
1つのモナドのように動作するFuture [Option [T]]に小さなラッパーを作成しました(モナドの法則をチェックする人はいませんが、map、flatMap、foreach、filterなどがあります)- MaybeLater =。非同期オプションよりもはるかに多くの動作をします。
そこには臭いコードがたくさんありますが、少なくとも例としては役立つでしょう。ところで:多くの未解決の質問があります( ここ の例)
https://github.com/qifun/stateless-future
またはhttps://github.com/scala/async
を使用してA-Normal-Form
変換を行う方が簡単です。