Scala для понимания с будущим и вариантами

object Main extends App {
  val p1 = Promise[Option[String]]()
  val p2 = Promise[Option[String]]()
  val f1 = p1.future
  val f2 = p2.future

  val res = (for{
    file1Opt <- f1
    file2Opt <- f2
    file1 <- file1Opt
    file2 <- file2Opt
  } yield {
    combineFiles(file1, file2)
  }).fallbackTo(Future.successful("Files not found"))

  Thread.sleep(2000)
  println("XXXXXXXXXXXXXXXXXXX")

  p1.success(Some("file one"))
  p2.success(Some("file two"))

  val finalData = res.map(s =>
    s + " " + "add more data to the file"
  ) 

  finalData.map(println(_))

  def combineFiles(f1: String, f2: String): String = {
    f1 + " " + f2
  }
}

У меня есть две функции, которые возвращают Future[Option[String]], и мне нужно объединить две строки в одну строку.

Я хочу, чтобы вывод был комбинацией двух строк и нижнего колонтитула: «файл, один файл, два, добавьте больше данных в файл» или по умолчанию, когда один или оба Futures возвращают None: «Файлы не найдены, добавьте больше данных в файл».

Как этого добиться?

Ошибка компилятора:

Error:(16, 11) type mismatch;
found   : Option[String]
required: scala.concurrent.Future[?]
file1 <- file1Opt
      ^ 

person An Illusion    schedule 26.05.2016    source источник


Ответы (3)


Что ж, не прибегая к каким-либо замысловатым преобразованиям монад и прочему, можно просто вложить for вложений. Будет более многословно, но без лишних зависимостей.

val res = (for{ 
  file1Opt <- f1
  file2Opt <- f2
} yield for {
  file1 <- file1Opt
  file2 <- file2Opt
} yield combineFiles(file1, file2))
.fallbackTo(Future.successful(Some("Files not found")))
//or, alternatively, .fallbackTo(Future.successful(None))

В конечном счете, проблема здесь в том, что вы пытаетесь объединить Future и Option в одном понимании for. Это просто не работает по причинам, упомянутым другими респондентами. Вложенность, однако, работает просто отлично.

Недостатком вложенности является то, что вы получаете очень сложные структуры данных, которые может быть непросто использовать где-либо еще в вашей программе. Вы должны подумать о том, как бы вы их сгладили, т.е. перейти от Future[Option[String]] к просто Future[String]. В вашем конкретном случае вы можете сделать что-то вроде этого: res.map(_.getOrElse("")).

Ладно, 2 уровня вложенности — это нормально, но если вы вкладываете больше, рассмотрите возможность выравнивания этой иерархии, прежде чем позволить своим коллегам разобраться с ней. :)

person Haspemulator    schedule 27.05.2016

Как упоминал alf в своем ответе, вы можете использовать для этого трансформеры монад, в этом случае OptionT.

Пример использования cats:

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import cats.data.OptionT
import cats.implicits._

val file1: Future[Option[String]] = Future.successful(Some("file1"))
val file2: Future[Option[String]] = Future.successful(Some("file2"))

val combinedOT: OptionT[Future, String] =
  for {
    f1 <- OptionT(file1)
    f2 <- OptionT(file2)
  } yield s"$f1 $f2"

val combinedFO: Future[Option[String]] = combinedOT.value
val combinedF: Future[String] = combinedOT.getOrElse("Files not found")

Обратите внимание, что если вы используете кошек, вы можете заменить for comprehension в combinedOT2 с помощью декартова построителя (|@|), потому что file2 не зависит от file1 :

val combinedOT2: Future[Option[String]] = 
  (OptionT(file1) |@| OptionT(file2)).map(_ + " " + _).value

Вы по-прежнему можете использовать fallbackTo, если "комбинированный" Future терпит неудачу, хотя, вероятно, лучше использовать recover или recoverWith, чтобы фактически проверить, какие Throwable вы хотите восстановить.

person Peter Neyens    schedule 26.05.2016

Я думаю, что именно эта проблема освещена в это сообщение в блоге 47deg, а также в этот: монады не составляются, поэтому вам нужен преобразователь из одной монады в другую, так как нет flatMap операции, которая (плоско) отображала бы Future в Option.

person alf    schedule 26.05.2016