web-dev-qa-db-ja.com

先物-マップvsフラットマップ

mapおよびflatMapに関するドキュメントを読みましたが、flatMapは、Futureパラメーターを受け取り、別のFutureを返す操作に使用されることを理解しています。私が完全に理解していないのは、なぜこれをしたいのかということです。次の例をご覧ください。

  1. ユーザーが「やること」を要求するWebサービスをヒットする
  2. ファイルをダウンロードします(遅い)
  3. ファイルを処理します(CPUに負荷がかかります)
  4. 結果をレンダリングする

将来を使用してファイルをダウンロードすることを理解していますが、それを再処理する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)を追加することで、両方のケースでダウンロードすると、processrenderが同じスレッドで(_ExecutionContext.Implicits.global_を使用して)発生することがわかります。

flatMapを使用して、単にdownloadFileprocessFileを分離し、processFileからマップされていなくても、Futureが常にdownloadFileで実行されるようにしますか?

23
user404345

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))
}
_
24
Peter Neyens

将来がある場合は、_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内包表記を使用すると、フードの下でflatMapmapと一緒に使用されます。

_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)))
_
26
mattinbits
def flatMap[B](f: A => Option[B]): Option[B] = 
  this match {
    case None => None
    case Some(a) => f(a)
  }

これは、OptionでflatMapがどのように機能するかを示す簡単な例です。これは、理解を深めるのに役立ちます。

1
Dhiraj Himani