Есть ли для понимания что-то вроде flatYield?

У меня есть код вроде

//all data have different types
val data1Future = loadData1(params)
val data2Future = loadData2(params)
val data3Future = loadData3(params)

def saveResult(rez): Future[_] = ???

data1Future.flatMap { data1 =>
  data2Future.flatMap { data2 =>
    data3Future.flatMap { data3 =>
      //do some computation
      //several rows and several vals
      val rez = ???
      saveResult(rez)
   }
  }
}

Но это немного некрасиво :) К сожалению, я не могу использовать для понимания, так как мне нужно что-то вроде "flatYield"

for {
  data1 <- data1Future
  data1 <- data1Future
  data1 <- data1Future
} flatYield {
  //do some computation
  //several rows and several vals
  val rez = data1 + data2 + data3
  saveResult(rez)
}

Вы знаете такой элегантный паттерн, как "для понимания", но с flatMap вместо map в конце цепочки?


person user3067313    schedule 13.11.2014    source источник


Ответы (3)


Ты можешь это сделать:

for {
  data1 <- data1Future
  data2 <- data2Future
  data3 <- data3Future
  rez = {
    //do some computation
    //several rows and several vals
    data1 + data2 + data3
  }
  r <- saveResult(rez)
} yield r

Это переводится как

data1Future.flatMap { data1 =>
  data2Future.flatMap { data2 =>
    data3Future.flatMap { data3 =>
      val rez = {
        //do some computation
        //several rows and several vals
        data1 + data2 + data3
      }
      saveResult(rez).map(r => r)
    }
  }
}

который изоморфен вашему коду.

person Vladimir Matveev    schedule 13.11.2014

Похоже, вам просто нужна еще одна строка в вашем for-comprehension, и все эти «другие вычисления» должны выполняться в другой функции, чтобы все было чисто:

for {
  data1 <- data1Future
  data2 <- data2Future
  data3 <- data3Future
  rez <- otherFunction(data1, data2, data3)
} yield rez 

def otherFunction(d1: ?, d2: ?, d3: ?): Future[?] = {
  //do some computation
  //several rows and several vals
}

В качестве альтернативы вы можете использовать что-то вроде этого:

(for {
  data1 <- data1Future
  data2 <- data2Future
  data3 <- data3Future
} yield {
  (data1, data2, data3)
}) flatMap { case (data1, data2, data3) =>
  //do some computation
  //several rows and several vals
  saveResult(rez)
}
person Michael Zajac    schedule 13.11.2014
comment
Неверная подпись для otherFunction. data[1-3] — это значения, извлеченные из фьючерсов, поэтому d[1-3] будет иметь любые типы OP. - person Gangstead; 13.11.2014
comment
Извините, я проголосовал за другой ответ и хочу объяснить, почему. Вашему первому решению нужна бессмысленная отдельная функция, второму требуется трижды называть одни и те же данные. - person user3067313; 14.11.2014
comment
@user3067313 user3067313 Еще одна функция не лишена смысла. Он разбивает код на логические части, что упрощает модульное тестирование и чтение. Что проще тестировать: четыре простые функции с понятными входными/выходными и пограничными случаями или одна огромная функция, в которой в четыре раза больше вещей может пойти не так, а непонятный метод вызвал поломку? - person Michael Zajac; 14.11.2014
comment
@m-z полностью согласен с вами в целом, но особенно в этом примере предполагается всего несколько строк кода обработки, и весь код уже упакован в функцию. Так что я не вижу смысла использовать calculateSomething и calculateSomethingInternal только для понимания. А также в этом случае мне нужно дважды назвать одни и те же данные. - person user3067313; 14.11.2014

Использование для понимания (например, вашей заявленной цели) эквивалентно определению фьючерсов в последовательности операций flatMap, которые будут ждать результата каждого фьючерса, прежде чем переходить к следующему. Поскольку ваши фьючерсы не зависят друг от друга, вы можете запустить их все, не дожидаясь завершения предыдущего - это то, что вы сделали в своем первоначальном примере.

Запустите их одновременно и сохраните фьючерсы в последовательности. Используйте Future.sequence, чтобы превратить набор фьючерсов в один Future, который не завершится, пока ВСЕ из них не завершатся (или какой-либо из них не завершится). Затем сохраните результат, когда будущее завершится.

val data1Future = loadData1(params) //type = Future[foo]
val data2Future = loadData2(params)
val data3Future = loadData3(params)

val listOfFutures = List(data1Future,data2Future,data3Future) //type = List[Future[foo]]

val futureOfList = Future.sequence(listOfFutures)  //type = Future[List[foo]]

futureOfList.onComplete( list => saveResult(list.reduce(_ + _))
person Gangstead    schedule 13.11.2014
comment
Хотя ваше первое предложение верно, Future в примере OP do выполняются одновременно, потому что loadData1(params), возвращающее Future, вызывается до flatMap. Они будут выполняться последовательно только в том случае, если loadData1(params) и т. д. будут вызваны в for-comprehension. - person Michael Zajac; 13.11.2014
comment
Да, но не для понимания, как в примере ОП того, что они пытаются сделать. Я обновил свой ответ. - person Gangstead; 13.11.2014
comment
1. dataN имеют разные типы, поэтому их нельзя объединить в обычную коллекцию 2. + это просто пример операции, извините за неточность. Может быть несколько строк кода обработки - person user3067313; 13.11.2014
comment
@Gangstead Futures ОП уже работают параллельно, но спасибо за гневное отрицание (извините, если это были не вы, но я даже не отрицал ваш ответ, это все еще действующий подход). flatMap ожидает завершения первого Future для выполнения, но это не препятствует запуску следующего Future, так как он начался до for-comprehension. Понимание по-прежнему будет занимать столько времени, сколько самый медленный Future. - person Michael Zajac; 13.11.2014
comment
Кроме того, Future.sequence в любом случае вызывает flatMap внутренне при сворачивании List[Future[A]], так что по сути это одно и то же. - person Michael Zajac; 13.11.2014
comment
Извините, ОП, я должен был получить разъяснения по этому поводу. Я предположил тот же тип, основываясь на вашем примере, и мой ответ во многом зависит от этого. @ m-z в вашем комментарии была ошибка. Я уточнил и еще раз уточнил, что способ, которым ОП определяет фьючерсы в первой части их примера, выполняется параллельно, но не во второй части их примера. Моя формулировка все еще отстой. Мое решение было тем, о чем просил ОП: элегантный вариант, но он предполагал фьючерсы одного типа, чтобы они могли попасть в коллекцию. Я не говорил, что это не работает с плоскими картами. - person Gangstead; 13.11.2014