Scala Параллельные коллекции. Как вернуться раньше?

У меня есть список возможных значений ввода

val inputValues = List(1,2,3,4,5)

У меня есть очень длинная функция вычисления, которая дает мне результат

def reallyLongFunction( input: Int ) : Option[String] = { ..... }

Используя scala параллельные коллекции, я легко могу сделать

inputValues.par.map( reallyLongFunction( _ ) )

Чтобы получить все результаты, параллельно. Проблема в том, что я действительно не хочу всех результатов, мне нужен только результат FIRST. Как только один из моих вкладов будет успешным, я хочу получить свой результат и хочу продолжить свою жизнь. Это сделало много дополнительной работы.

Итак, как мне получить лучшее из обоих миров? Я хочу

  • Получить первый результат, который возвращает что-то из моей длинной функции
  • Остановите все мои другие потоки от бесполезной работы.

Изменить - Я решил это как немой программист java, имея

@volatile var done = false;

который установлен и проверен внутри моего reallyLongFunction. Это работает, но не очень сильно scala. Хотелось бы лучше сделать это....

Ответы

Ответ 1

Я воспринял ваш вопрос так же, как huynhjl, но если вы просто хотите искать и отбрасывать None s, вы можете сделать что-то подобное, чтобы избежать необходимости повторять вычисление, когда найден подходящий результат:

class Computation[A,B](value: A, function: A => B) {
  lazy val result = function(value)
}

def f(x: Int) = {          // your function here
  Thread.sleep(100 - x)
  if (x > 5) Some(x * 10)
  else None
}

val list = List.range(1, 20) map (i => new Computation(i, f))  
val found = list.par find (_.result.isDefined) 
  //found is Option[Computation[Int,Option[Int]]]
val result = found map (_.result.get)
  //result is Option[Int]

Однако find для параллельных коллекций, похоже, делает много ненужной работы (см. этот вопрос), поэтому это может не сработать, с текущими версиями Scala по крайней мере.

Летучие флаги используются в параллельных коллекциях (посмотрите на источник для find, exists и forall), поэтому я думаю, что ваша идея хорошая. Это действительно лучше, если вы можете включить флаг в самой функции. Он убивает ссылочную прозрачность вашей функции (т.е. Для некоторых входов ваша функция теперь иногда возвращает None, а не Some), но поскольку вы отбрасываете остановленные вычисления, это не имеет значения.

Ответ 2

(Обновлено: нет, он не работает, не делает карту)

Будет ли работать что-то вроде:

inputValues.par.find({ v => reallyLongFunction(v); true })

Реализация использует это:

  protected[this] class Find[U >: T](pred: T => Boolean, protected[this] val pit: IterableSplitter[T]) extends Accessor[Option[U], Find[U]] {
    @volatile var result: Option[U] = None
    def leaf(prev: Option[Option[U]]) = { if (!pit.isAborted) result = pit.find(pred); if (result != None) pit.abort }
    protected[this] def newSubtask(p: IterableSplitter[T]) = new Find(pred, p)
    override def merge(that: Find[U]) = if (this.result == None) result = that.result
  }

который выглядит довольно похожим по духу на ваш @volatile, за исключением того, что вам не нужно его смотреть; -)

Ответ 3

Если вы хотите использовать неосновную библиотеку, я думаю, что Futures будет хорошим выбором для этой задачи. Например:

... оба из которых, как представляется, позволяют использовать функциональность, которую вы ищете.