map
およびflatMap
に関するドキュメントを読みましたが、flatMap
は、Future
パラメーターを受け取り、別のFuture
を返す操作に使用されることを理解しています。私が完全に理解していないのは、なぜこれをしたいのかということです。次の例をご覧ください。
将来を使用してファイルをダウンロードすることを理解していますが、それを再処理する2つのオプションがあります:
_val downloadFuture = Future { downloadFile }
val processFuture = downloadFuture map { processFile }
processFuture onSuccess { case r => renderResult(r) }
_
または
_val downloadFuture = Future { // download the file }
val processFuture = downloadFuture flatMap { Future { processFile } }
processFuture onSuccess { case r => renderResult(r) }
_
デバッグステートメント(Thread.currentThread().getId
)を追加することで、両方のケースでダウンロードすると、process
とrender
が同じスレッドで(_ExecutionContext.Implicits.global
_を使用して)発生することがわかります。
flatMap
を使用して、単にdownloadFile
とprocessFile
を分離し、processFile
からマップされていなくても、Future
が常にdownloadFile
で実行されるようにしますか?
processFile
からマップされていなくても、Future
で常にdownloadFile
で実行されるようにしますか?
はい、それは正しいです。
ただし、ほとんどの場合、_Future { ... }
_を直接使用しない場合は、Future
を返す(他のライブラリまたは独自の)関数を使用します。
次の機能を想像してください:
_def getFileNameFromDB{id: Int) : Future[String] = ???
def downloadFile(fileName: String) : Future[Java.io.File] = ???
def processFile(file: Java.io.File) : Future[ProcessResult] = ???
_
flatMap
を使用してそれらを組み合わせることができます。
_val futResult: Future[ProcessResult] =
getFileNameFromDB(1).flatMap( name =>
downloadFile(name).flatMap( file =>
processFile(file)
)
)
_
または、理解のために
_val futResult: Future[ProcessResult] =
for {
name <- getFileNameFromDB(1)
file <- downloadFile(name)
result <- processFile(file)
} yield result
_
ほとんどの場合、onSuccess
(またはonComplete
)を呼び出さないでしょう。これらの関数のいずれかを使用して、Future
が終了したときに実行されるコールバック関数を登録します。
この例でファイル処理の結果をレンダリングしたい場合、futResult.onSuccess(renderResult)
を呼び出す代わりに_Future[Result]
_のようなものを返します。最後の場合、戻り値の型はUnit
になるため、実際に何かを返すことはできません。
Playフレームワークでは、これは次のようになります。
_def giveMeAFile(id: Int) = Action.async {
for {
name <- getFileNameFromDB(1)
file <- downloadFile(name)
processed <- processFile(file)
} yield Ok(processed.byteArray).as(processed.mimeType))
}
_
将来がある場合は、_Future[HttpResponse]
_とし、ファイルに本文を書き込むなど、準備ができたときにその結果をどう処理するかを指定する場合は、responseF.map(response => write(response.body)
。ただし、write
がfutureを返す非同期メソッドでもある場合、このmap
呼び出しは_Future[Future[Result]]
_のような型を返します。
次のコードでは:
_import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
val numF = Future{ 3 }
val stringF = numF.map(n => Future(n.toString))
val flatStringF = numF.flatMap(n => Future(n.toString))
_
stringF
はタイプ_Future[Future[String]]
_で、flatStringF
はタイプ_Future[String]
_です。ほとんどの人が同意しますが、2番目の方がより便利です。したがって、フラットマップは、複数の先物を一緒に構成するのに役立ちます。
Futuresでfor
内包表記を使用すると、フードの下でflatMap
がmap
と一緒に使用されます。
_import scala.concurrent.{Await, Future}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
val threeF = Future(3)
val fourF = Future(4)
val fiveF = Future(5)
val resultF = for{
three <- threeF
four <- fourF
five <- fiveF
}yield{
three * four * five
}
Await.result(resultF, 3 seconds)
_
このコードは60になります。
内部では、scalaはこれを
_val resultF = threeF.flatMap(three => fourF.flatMap(four => fiveF.map(five => three * four * five)))
_
def flatMap[B](f: A => Option[B]): Option[B] =
this match {
case None => None
case Some(a) => f(a)
}
これは、OptionでflatMapがどのように機能するかを示す簡単な例です。これは、理解を深めるのに役立ちます。