Почему этот список будущих преобразований в список будущего компилируется и работает?

Отказ от ответственности: приведенный ниже фрагмент кода относится к одному из текущих курсов Coursera. Будем считать, что оно размещено только в учебных целях и не должно использоваться для отправки в качестве решения домашнего задания.

Как указано в комментарии ниже, нам нужно преобразовать список фьючерсов в один фьючерс списка. Более того, результирующее Future должно потерпеть неудачу, если не удалось хотя бы одно из входных Futures.

Я встретил следующую реализацию, и я не понимаю ее полностью.

/** Given a list of futures `fs`, returns the future holding the list of values of all the futures from `fs`.
 *  The returned future is completed only once all of the futures in `fs` have been completed.
 *  The values in the list are in the same order as corresponding futures `fs`.
 *  If any of the futures `fs` fails, the resulting future also fails.
 */
def all[T](fs: List[Future[T]]): Future[List[T]] = 
             fs.foldRight(Future(Nil:List[T]))((f, fs2) =>
  for {
    x <- f
    xs <- fs2
  } yield (x::xs))

В частности, я не понимаю в нем следующие вещи:

  1. Где происходит Future[T] -> T преобразование? Похоже, что xs <- fs2 — это единственное место, где мы касаемся исходного Futures, и каждый из xs должен быть Future[T] (но почему-то он становится просто T).
  2. Как обрабатываются сбои? Похоже, что результирующий объект Future действительно терпит неудачу, когда один из входных Futures терпит неудачу.

person Roman    schedule 01.12.2013    source источник
comment
Здесь уже дан ответ: stackoverflow.com/a/20280032/332087   -  person Shyamendra Solanki    schedule 01.12.2013


Ответы (2)


1) Скажем, f — это Future[T], тогда запись

for {
 t <- f
}  yield List(t)

будет хранить результат Future f в t, поэтому t имеет тип T. yield превращает его в List[T], а тип всего for-comprehension оказывается Future[List[T]]. Таким образом, для понимания вы извлекаете свои T из своего Futures, что-то делаете с ними и возвращаете их в Будущее (хорошо, я немного упрощаю здесь).

Это эквивалентно

f.map(t => List(t))

2) Если ваше Future f содержит сбой, то for-comprehension просто вернет это сбойное будущее вместо выполнения yield.

В общем, for-comprehension в Scala — это просто сахар, который можно переписать с помощью map, flatMap, filter, foreach.

person vptheron    schedule 01.12.2013

Я англоговорящий правша, поэтому обычно я сгибаю влево, но каждый шаг сгиба выглядит так:

Fn flatMap ((x: T) => Fs map (xs => x :: xs))

Ваше значение x.

Функция применяется в случае успеха, что объясняет, почему неудача останавливает вас:

scala> timed(Await.ready(all(List(Future{Thread sleep 5*1000; 1},Future(2),Future{Thread sleep 10*1000; 3})), Duration.Inf))
res0: (Long, scala.concurrent.Awaitable[List[Int]]) = (10002419021,scala.concurrent.impl.Promise$DefaultPromise@2a8025a0)

scala> timed(Await.ready(all(List(Future{Thread sleep 5*1000; 1},Future(???),Future{Thread sleep 10*1000; 3})), Duration.Inf))
res1: (Long, scala.concurrent.Awaitable[List[Int]]) = (5000880298,scala.concurrent.impl.Promise$DefaultPromise@3750d517)

Обратите внимание, что в неудачной версии возникает короткое замыкание.

См. обе части информации в ScalaDoc для flatMap.

Редактировать: я говорил осторожно, потому что это работа Coursera, но, проще говоря, это требование не выполняется: «Возвращенное будущее будет завершено только после того, как все фьючерсы в fs будут завершены».

person som-snytt    schedule 01.12.2013