Не может сгладить Попытка в понимании
Это сочетание стилистического вопроса и моих попыток расширить понимание Scala.
У меня есть список, содержащий Future, я хочу вычислить значения фьючерсов, преобразовать их в Option и выровнять список, используя для понимания:
import scala.util.Try
import scala.concurrent._
import ExecutionContext.Implicits.global
val l= List(Future.successful(1),Future.failed(new IllegalArgumentException))
implicit def try2Traversible[A](xo: Try[A]): Iterable[A] = xo.toOption.toList
val res = for{f <- l; v <- f.value} yield v
scala> res: List[scala.util.Try[Int]] = List(Success(1), Failure(java.lang.IllegalArgumentException))
res.flatten
res16: List[Int] = List(1)
То, что я хочу сделать, - это получить плоскую сцену для понимания, у кого-нибудь есть какие-либо предложения?
3 ответа
Делать это неправильно:
for{f <- l; v <- f.value} yield v
Похоже, что работает в вашем случае только потому, что фьючерсы уже реализованы, поэтому их value
член определен. Однако в общем случае они могут еще не быть выполнены при выполнении для понимания, и, таким образом, value
вернусь None
(несмотря на то, что в какой-то момент они в конечном итоге будут выполнены). Например, попробуйте это в REPL:
val f1 = Future{
Thread.sleep(3000) // Just a test to illustrate, never do this!
1
}
val f2 = Future{
Thread.sleep(3000) // Just a test to illustrate, never do this!
throw new IllegalArgumentException
}
val l = List( f1, f2 )
for{f <- l; v <- f.value} yield v
Результатом является пустой список, потому что ни один из фьючерсов в l
выполнено еще. Затем подождите немного (не более 3 секунд) и повторите выполнение для понимания (последняя строка), и вы получите непустой список, потому что фьючерсы наконец-то выполнены.
Чтобы это исправить, вам придется либо заблокировать (то есть дождаться выполнения всех фьючерсов), используя scala.concurrent.Await
или оставайтесь в асинхронном мире, используя что-то вроде Future.map
или же Future.flatMap
, Например, если вы хотите заблокировать, вы можете сделать:
Await.result( Future.sequence( l ), duration.Duration.Inf )
Await.result
ждет результата будущего, позволяя перейти из асинхронного мира в синхронный мир. Результатом вышесказанного является List[Int]
Проблема в том, что вы теряете случаи сбоев (результат не List[Try[Int]]
как вы хотели), и фактически сбросит первое исключение. Чтобы исправить это, вы можете использовать этот вспомогательный метод, который я опубликовал в другом ответе: /questions/32386964/kak-prodolzhit-vyipolnenie-posledovatelnosti-future-nesmotrya-na-neudachu/32386975#32386975 Используя его, вы можете сделать:
Await.result( Future.sequence( l map mapValue ), duration.Duration.Inf )
Это будет ждать, пока все фьючерсы не будут выполнены (либо с правильным значением, либо с ошибкой) и вернет ожидаемое List[Try[Int]]
Идея состоит в том, чтобы перейти к Try
объект, как будто это было Option
(т. е. набор из 0 или 1 элемента) внутри самого понимания. Чтобы этот обход работал, должно быть преобразование из Try
введите в Option
тип.
Это должно работать:
implicit def try2option[A](xo: Try[A]) = xo.toOption
val res = for (f <- l; t <- f.value; x <- t) yield x
Вы должны держать Future
вокруг вашего окончательного результата, чтобы сохранить асинхронный характер вычислений.
Хороший способ сделать это (и получить Future[List[Int]]
) будет (вероятно, что вы пытались):
for {
f <- l // Extract individual future
v <- f // Extract value from future
} yield v
К сожалению, это означает:
l.flatMap(f => f.map(v => v))
Который не работает, потому что Future
не наследует GenTraversableOnce
(и, вероятно, не следует), но List
нужна эта черта для его flatMap
,
Мы можем использовать Future.sequence
сделать это:
Future.sequence(l)
Это вернет Future[List[Int]]
которая завершается только после завершения всех фьючерсов и будет содержать все значения фьючерсов, которые были успешно завершены.