在scala Future中执行类似DAG的操作

在scala Future中执行类似DAG的操作,scala,future,directed-acyclic-graphs,Scala,Future,Directed Acyclic Graphs,我正在处理一个用例,在这个用例中,我必须使用scala Future执行相互依赖的操作(定义为有向无环图)。基本上,每个操作(比如DAG的节点)都将在将来执行,并且一旦当前节点将来完成,将触发后续的从属节点(它们也应该在将来)。这将一直持续到每个节点完成处理或其中一个节点出现故障。到目前为止,我有(最少的代码): 这是解决这个问题的正确方法吗(在回调中调用另一个Future)?再一次,我没有正确的方法停止其他正在运行的节点,一旦其中一个节点处理失败 这可以用未来的合成来解决吗?如果是,我怎样才能

我正在处理一个用例,在这个用例中,我必须使用scala Future执行相互依赖的操作(定义为有向无环图)。基本上,每个操作(比如DAG的节点)都将在将来执行,并且一旦当前节点将来完成,将触发后续的从属节点(它们也应该在将来)。这将一直持续到每个节点完成处理或其中一个节点出现故障。到目前为止,我有(最少的代码):

这是解决这个问题的正确方法吗(在回调中调用另一个Future)?再一次,我没有正确的方法停止其他正在运行的节点,一旦其中一个节点处理失败

这可以用未来的合成来解决吗?如果是,我怎样才能做到

谢谢,

Pravin

这里有一个更实用的方法:我们可以使用泛型类型,而不是使用
单元作为
运行的评估结果。通常,您希望从功能上处理
未来的结果,而不是它的副作用

我添加了类型注释和描述性变量名,以便更容易理解。我还添加了一些案例来说明它将如何失败。当发生故障时,您还可以选择恢复而不是失败。然而,对于这个问题,如果子计算依赖于父值,那么失败可能更合理

import scala.concurrent.{Await, Future}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.util.Try

case class Node[T](value: T, children: List[Node[T]])

object DagFuture extends App {

  def run[A, B](node: Node[A], result: B)(nodeEval: (Node[A], B) => B)(aggregator: List[B] => B): Future[B] = {
    val nodeResult: Future[B] = Future(nodeEval(node, result))
    val allResults: Future[List[B]] = nodeResult.flatMap(r => Future.sequence(nodeResult :: node.children.map(x => run(x, r)(nodeEval)(aggregator))))
    val finalResult: Future[B] = allResults.map(cl => aggregator(cl))
    finalResult
  }

  val debugSum = (l: List[Int]) => {
    println(s"aggregating: $l")
    l.sum
  }

  def debugNodeEval(f: (Node[Int], Int) => Int)(n: Node[Int], r: Int) = {
    val eval = Try { f(n, r) }
    println(s"node: $n, result: $r, eval: $eval")
    eval.get
  }

  val debugNodeEvalDefault = debugNodeEval((n, r) => n.value + r) _

  val singleNodeDag = Node(1, Nil)
  val multiNodeDag = Node(1, List(Node(20, Nil), Node(300, Nil)))

  println("\nSINGLE NODE DAG EXAMPLE:")
  val singleNodeFuture = run(singleNodeDag, 0)(debugNodeEvalDefault)(debugSum)
  val singleNodeResult = Await.result(singleNodeFuture, 5 seconds)
  println(s"Single node result: $singleNodeResult")

  println("\nDAG PATH LENGTH EXAMPLE:")
  val pathLengthFuture = run(multiNodeDag, 0)(debugNodeEvalDefault)(debugSum)
  val pathLengthResult = Await.result(pathLengthFuture, 5 seconds)
  println(s"Path length: $pathLengthResult")

  println("\nFAILED DAG ROOT NODE EXAMPLE:")
  val failedRootNodeFuture = run(multiNodeDag, 0)(debugNodeEval((n, r) => throw new Exception))(debugSum)
  val failedRootNodePromise = Await.ready(failedRootNodeFuture, 5 seconds)
  println(s"Failed root node: ${failedRootNodePromise.value}")

  println("\nFAILED DAG CHILD NODE EXAMPLE:")
  val failedChildNodeFuture = run(multiNodeDag, 0)(debugNodeEval((n, r) => if (n.value == 300) throw new Exception else n.value + r))(debugSum)
  val failedChildNodePromise = Await.ready(failedChildNodeFuture, 5 seconds)
  println(s"Failed child node: ${failedChildNodePromise.value}")
}
打印此文件:

SINGLE NODE DAG EXAMPLE:
node: Node(1,List()), result: 0, eval: Success(1)
aggregating: List(1)
Single node result: 1

DAG PATH LENGTH EXAMPLE:
node: Node(1,List(Node(20,List()), Node(300,List()))), result: 0, eval: Success(1)
node: Node(20,List()), result: 1, eval: Success(21)
node: Node(300,List()), result: 1, eval: Success(301)
aggregating: List(301)
aggregating: List(21)
aggregating: List(1, 21, 301)
Path length: 323

FAILED DAG ROOT NODE EXAMPLE:
node: Node(1,List(Node(20,List()), Node(300,List()))), result: 0, eval: Failure(java.lang.Exception)
Failed root node: Some(Failure(java.lang.Exception))

FAILED DAG CHILD NODE EXAMPLE:
node: Node(1,List(Node(20,List()), Node(300,List()))), result: 0, eval: Success(1)
node: Node(20,List()), result: 1, eval: Success(21)
aggregating: List(21)
node: Node(300,List()), result: 1, eval: Failure(java.lang.Exception)
Failed child node: Some(Failure(java.lang.Exception))
TL;博士


粗略地说,它只是一个
Future.flatMap(result=>Future.sequence(children…)
。当父级
Future
完成时,其结果将在
flatMap
中传递给子级计算。如果父级
Future
失败,则整个计算也会失败<代码>序列
将来自
未来
s列表的结果组合成单个
未来
。子
Future
是其子项的父项,以此类推。因此,同样的故障模式也适用。

每个功能计算都是一个DAG,您可以简单地将计算包装到
Future
s和
flatMap
嵌套的
Future
s中。如果你想走“硬核”功能之路,你可能想看看
scalaz
@ziggystart中的
FreeAp
,我不知道如何使用flatMap实现这一点,你有什么想法(可能是一段代码示例)?
flatMap
将不起作用,因为
run
的返回类型是
Unit
not
Future
。如果
run
的签名必须是您定义的方式,我看不出您的实现有任何固有的错误。任何其他实现都只是风格问题…@AlekseyIzmailov
wait.result(alekseyFutureImplementation,Duration.Inf)
:)
SINGLE NODE DAG EXAMPLE:
node: Node(1,List()), result: 0, eval: Success(1)
aggregating: List(1)
Single node result: 1

DAG PATH LENGTH EXAMPLE:
node: Node(1,List(Node(20,List()), Node(300,List()))), result: 0, eval: Success(1)
node: Node(20,List()), result: 1, eval: Success(21)
node: Node(300,List()), result: 1, eval: Success(301)
aggregating: List(301)
aggregating: List(21)
aggregating: List(1, 21, 301)
Path length: 323

FAILED DAG ROOT NODE EXAMPLE:
node: Node(1,List(Node(20,List()), Node(300,List()))), result: 0, eval: Failure(java.lang.Exception)
Failed root node: Some(Failure(java.lang.Exception))

FAILED DAG CHILD NODE EXAMPLE:
node: Node(1,List(Node(20,List()), Node(300,List()))), result: 0, eval: Success(1)
node: Node(20,List()), result: 1, eval: Success(21)
aggregating: List(21)
node: Node(300,List()), result: 1, eval: Failure(java.lang.Exception)
Failed child node: Some(Failure(java.lang.Exception))
def run[A, B](node: Node[A], result: B)(nodeEval: (Node[A], B) => B)(aggregator: Traversable[B] => B): Future[B] = {
    val nodeResult = Future(nodeEval(node, result))
    val allResults = nodeResult flatMap { r => Future.sequence(nodeResult :: node.children.map { x => run(x, r)(nodeEval)(aggregator) }) }
    allResults map aggregator
  }