Не могу сгладить Попытку понять

Это сочетание стилистического вопроса и моих попыток расширить мое понимание Scala.

У меня есть список, содержащий Future, я хочу вычислить значения Futures, преобразовать их в 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)

Что я хочу сделать, так это перевести стадию сглаживания в стадию для понимания, у кого-нибудь есть предложения?


person irishjava    schedule 15.04.2013    source источник


Ответы (3)


Это неправильно:

for{f <- l; v <- f.value} yield v

Кажется, это работает в вашем случае только потому, что фьючерсы уже выполнены, поэтому их член value определен. Однако в общем случае они могут еще не выполняться, когда вы выполняете for понимание, и, таким образом, 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]], как вы хотели), и фактически повторно генерируете первое исключение. Чтобы исправить это, вы можете использовать этот вспомогательный метод, который я опубликовал в другом ответе: https://stackoverflow.com/a/15776974/1632462 С его помощью вы можете:

Await.result( Future.sequence( l map mapValue ), duration.Duration.Inf )

Это будет ждать, пока все фьючерсы не будут выполнены (либо с правильным значением, либо с ошибкой), и вернет ожидаемый List[Try[Int]]

person Régis Jean-Gilles    schedule 15.04.2013

Идея состоит в том, чтобы перейти к объекту Try, как если бы он был Option (т.е. набором из 0 или 1 элементов) внутри самого for-computing. Чтобы этот обход работал, необходимо преобразование типа Try в тип Option.

Это должно работать:

implicit def try2option[A](xo: Try[A]) = xo.toOption

val res = for (f <- l; t <- f.value; x <- t) yield x
person axel22    schedule 15.04.2013

Вы должны сохранить 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.

However, we can do this manually: val res = l.foldRight(Future.successful(List.empty[Int])) { case (x,xs) => xs.flatMap(vxs => x.map(vx => vx :: vxs)) }


Для этого мы можем использовать Future.sequence:

Future.sequence(l)

Это вернет Future[List[Int]], который завершится только тогда, когда все фьючерсы будут завершены, и будет содержать все значения фьючерсов, которые завершились успешно.

person gzm0    schedule 16.04.2013
comment
Future.sequence следует использовать для преобразования списка фьючерсов в список будущих. - person Aaron Novstrup; 16.04.2013
comment
Хахаха, понятно. Изобрел колесо. Обновлено. - person gzm0; 17.04.2013