Развертывание функций без параметров в scala с фьючерсами - PullRequest
1 голос
/ 05 февраля 2020

Я пытаюсь создать цепочку преобразований, чтобы определить возможные преобразования для заданных функций:

type Transformation[T] = T => Future[T]

def transformationChain[T](chain: Seq[Transformation[T]]): Transformation[T] = {
}


val t1: Transformation[Int] = t => Future.successful(t + t)
val t2: Transformation[Int] = _ => Future.failed(new NoSuchElementException)
val t3: Transformation[Int] = t =>
    if (t > 2) Future.successful(t * t)
    else Future.failed(new NoSuchElementException)

val tc = transformationChain(Seq(t1, t2, t2, t3))
val tc2 = transformationChain(Seq(t2, t2, t2))
val tc3 = transformationChain(Seq(t2, t3, t1))

println(Await.result(tc(2), 5.seconds))  // 16
println(Await.result(tc2(2), 5.seconds)) // throw NoSuchElementException
println(Await.result(tc3(2), 5.seconds)) // 4

Проблема в том, что я не понимаю, как развернуть эти функции в методе "formationChain "для отправки результат каждой следующей функции в цепочке, вызывая их в l oop или рекурсивно.

Ответы [ 2 ]

1 голос
/ 05 февраля 2020
import scala.concurrent.{ ExecutionContext, Future }
import scala.util.Try

import ExecutionContext.Implicits.global

object Transformations {
  type Transformation[T] = T => Future[T]

  private object DummyException extends Exception
  private val notReallyAFailedFuture: Future[Throwable] = Future.failed(DummyException)

  def transformationChain[T](chain: Seq[Transformation[T]])(implicit ectx: ExecutionContext): Transformation[T] = t =>
    if (chain.nonEmpty) {
      val initialFut = Future.successful(t)
      // resultFut will succeed if any of the transformations in the chain succeeded
      // lastFailure will fail if all of the transformations succeeded, otherwise it has the last failure
      val (resultFut: Future[T], lastFailure: Future[Throwable]) =
        chain.foldLeft((Future.failed[T](DummyException), notReallyAFailedFuture)) { (acc, v) =>
          val thisResult = acc._1.recoverWith {
            case _ => initialFut
          }.flatMap(v)
          val lastFailure = thisResult.failed.recoverWith { case _ => acc._2 }
          (thisResult.recoverWith { case _ => acc._1 }, lastFailure)
        }
      resultFut.recoverWith {
        case _ =>
          lastFailure.flatMap(Future.failed)
      }
    } else Future.successful(t)   // What to do with an empty chain is unspecified

  def main(args: Array[String]): Unit = {
    import scala.concurrent.Await
    import scala.concurrent.duration._

    val t1: Transformation[Int] = t => Future.successful(t + t)
    val t2: Transformation[Int] = _ => Future.failed(new NoSuchElementException)
    val t3: Transformation[Int] = t =>
      if (t > 2) Future.successful(t * t)
      else Future.failed(new NoSuchElementException)

    val tc1 = transformationChain(Seq(t1, t2, t2, t3))
    val tc2 = transformationChain(Seq(t2, t2, t2))
    val tc3 = transformationChain(Seq(t2, t3, t1))

    println(Try(Await.result(tc1(2), 5.seconds)))
    println(Try(Await.result(tc2(2), 5.seconds)))
    println(Try(Await.result(tc3(2), 5.seconds)))
  }
}

В этой реализации предполагается, что:

  • Если несколько преобразований завершатся неудачно, вернуть последний сбой
  • Если цепочка пуста, предположить преобразование идентификатора

transformationChain теперь требует неявного ExecutionContext для планирования «склеивания» функций между фьючерсами на трансформацию. В Scala 2.13+ контекст scala.concurrent.ExecutionContext.parasitic на самом деле является довольно хорошим выбором для выполнения этих быстрых преобразований (и полезен практически ни для чего другого).

Для того, чтобы получить все println s для выполнить, я обернул Await.result s в Try.

В целях краткости, есть некоторое использование неудачного Future для представления отсутствия результата.

1 голос
/ 05 февраля 2020

То, что вы описываете как Преобразование (так что функция A => F [B] ) часто называют стрелками Клейсли.

Кошки Библиотека имеет тип данных, что облегчает работу с этими функциями. Например, у него есть метод andThen , который разрешает составление этих функций:

import cats.data.Kleisli
import cats.implicits._

val t1: Transformation[Int] = t => Future.successful(t + t)
val t2: Transformation[Int] = _ => Future.failed(new NoSuchElementException)

Kleisli(t1).andThen(Kleisli(t2))

Единственная проблема заключается в том, что одно из ваших преобразований может вернуть неудачное будущее, что приведет к короткому замыканию. -контур всей цепи. Мы можем исправить это с помощью recoverWith .

Итак, в конечном итоге translationChain может выглядеть так:

def transformationChain[T](chain: Seq[Transformation[T]]): Transformation[T] =
    t =>
      chain
         //wrap the function in Kleisli and then use replace failed futures with succeeded
         //future, that are passing value over
        .map(Kleisli(_).recoverWith {
          case _ => Kleisli(x => Future.successful(x))
        })
        .reduce(_ andThen _) //combine all elements with andThen
        .apply(t)

Работает нормально для случаев 1 и 3, но не работает в случае 2, так как он просто вернет переданное значение.

println(Await.result(tc(2), 5.seconds)) // 16
println(Await.result(tc3(2), 5.seconds)) // 4
println(Await.result(tc2(2), 5.seconds)) // 2
...