
皆さん、こんばんは。 今日は、 Futureと呼ばれるこのようなすばらしいscalaコアの部分に光を当てたいと思います。 実際には公式Webサイトにドキュメントがありますが、 イベント駆動型アプローチを使用してそれを操作する方法の説明があります。 しかし、 Futureもモナドです。 そして、この記事では、例を挙げて、この方法でそれらを使用する方法を少し説明したかったのです(むしろ、この問題に対する私のビジョン)。 みんなに猫の下の質問を読んでもらいます。
ここにないもの
この記事では、コールバック、約束、この優れた競争モデルの達成方法などについては説明しません。
どうなる
そして、 Futureモナドに動作を与える関数についてだけ書いていきます。
地図
それでは、 apiを見てみましょう。
def map[S](f: (T) ⇒ S)(implicit executor: ExecutionContext): Future[S]
mapは何をしますか? Futureが成功した場合、 f関数が実行され、結果が渡されます。 そして、この結果は今後もパッケージ化されます。
Future(5) map (2*) map println
最終的に、このコードは画面に10を期待どおりに表示します。 結果がFailureの場合、関数は実行されません。
Future(5) map (_ / 0) map println
2番目の関数は例外をスローし、何も表示されません。
では、なぜmapを使用するのですか? -前の結果を必要とする順次操作の場合。
flatMap
経験豊富な読者は、モナドがモナドの内部にある可能性があることを理解しており、この問題を何らかの方法で修正する必要があります。
def flatMap[S](f: (T) ⇒ Future[S])(implicit executor: ExecutionContext): Future[S]
flatMapは、別のFutureを返す関数を受け取り、それを返します。
def f(a: Int): Future[Int] = Future(a * 5) Future(2) flatMap (f) map println
数字の10が表示され、実行に失敗した場合の動作はmapの場合と同じです。
Futureを返すチェーン操作の場合は、 flatMapを使用する必要があります。
のために
飛ぶ必要はありませんし、怒ってそれを書く必要はありません。関数ではなく、構文構造です。 はい、知っていますが、彼女について話さないのは愚かなことです
mapとflatMapについて説明したので、 for-comprehensionsの使用を検討する必要があります。
Futureで forを使用する悪い例と良い例を見てみましょう。
// for { a <- longComputations1() b <- longComputations2() c <- longComputations3() } yield a*b*c // val f1 = longComputations1() val f2 = longComputations2() val f3 = longComputations3() for { a <- f1 b <- f2 c <- f3 } yield a*b*c
両方の操作の結果は同じになりますが、...
どうして悪いの?
実は、答えは簡単です。
展開先:
つまり、全体ではなく、順番に呼び出されます。 2番目のオプションはこの問題を解決しますが、最善の方法ではないため、より良い方法です。
for { a <- longComputations1() b <- longComputations2() c <- longComputations3() } yield a*b*c
展開先:
longComputations1().flatMap { a => longComputations2().flatMap { b => longComputations3().flatMap { c => a*b*c } } }
つまり、全体ではなく、順番に呼び出されます。 2番目のオプションはこの問題を解決しますが、最善の方法ではないため、より良い方法です。
zip
def zip[U](that: Future[U]): Future[(T, U)]
以前は、複数のFutureを同時に使用する問題が考慮されていました。 まあ、 zipはこの問題を解決します。 彼は2つのFutureを取得し 、その結果をTuple2にパックします。 そして、これが今書かれている上記の例です:
longComputations1() zip longComputations2() zip longComputations3() map { case ((a, b), c) => a * b * c }
個人的には、私の意見では、すべてがずっときれいでシンプルです。
フィルターとwithFilter
def filter(p: (T) ⇒ Boolean)(implicit executor: ExecutionContext): Future[T] final def withFilter(p: (T) ⇒ Boolean)(implicit executor: ExecutionContext): Future[T]
ここではすべてが論理的であり、結果を取得してテストし、適合しない場合は、 失敗した Futureがあり、その中にNoSuchElementExceptionがパックされます。
recoverおよびrecoverWith
コードは非同期で実行されるため、例外の非同期制御が必要です。 そして、ここにあります:
def recover[U >: T](pf: PartialFunction[Throwable, U])(implicit executor: ExecutionContext): Future[U] def recoverWith[U >: T](pf: PartialFunction[Throwable, Future[U]])(implicit executor: ExecutionContext): Future[U]
例外の場合、部分関数が呼び出され、ある場合には値を返し、別のFutureでは値を返します。
Future(5) map (_ / 0) recover { case _ => 0 } map println
ここでは、例外が処理され、0が表示されます。
foreach
実際、それはマップであり、結果のみが新しいFutureにパッケージ化されず、単にUnitを返します:
def foreach[U](f: (T) ⇒ U)(implicit executor: ExecutionContext): Unit
実際、前の例は完全に正しいわけではなく、次のように書く方が良いでしょう。
Future(5) map (2*) foreach println
ここでは、1つの余分なFutureの作成を避けました。
より一般的な例
だから私たちは持っています:
def f1: Future[Double] def f2: Future[Double] def f3(a: Double): Future[Double] def f4: Future[Double] def f5(a: Double, b: Double, c: Double): Future[Double]
f2の実行結果はf3に渡す必要があり、 f1 、 f3 、およびf4の実行結果はf5に渡す必要があることがわかっています。 そして最終的に、結果は標準出力に出力されるはずです。 また、f3が例外をスローすることもあり、その場合は0が返されます。
行こう:
val r1 = f1() val r2 = f2() flatMap (f3) recover { case _: Exception => 0 } var r3 = f4() for { a <- r1 b <- r2 c <- r3 res <- f5(a, b, c) } yield println(res)
私が好む:
f1 zip f4 zip (f2() flatMap (f3) recover { case _: Exception => 0 }) flatMap { case ((a, b), c) => f5(a, b, c) } foreach println
そして、どのように記録しますか?
あとがき
だから私はFutureの単項関数を説明しようとしましたが、個人的には成功したと思います。 もちろん、たとえば、ヘルパークラスについてはまだ説明することがあります。ヘルパークラスには非常に興味深い重要な機能がありますが、これはすでに別の記事の資料です。
Futureをどのように使用していて、私が省略したコメントにコメントを書いてください。
ご清聴ありがとうございました。