Scala未来递归早期返回

时间:2014-04-28 13:48:05

标签: scala recursion concurrency

我正在尝试使用递归的未来调用来获取具有一定深度的树。 Futures.flatMap提前返回,但递归的期货会在提前返回后继续完成

我们有一个带边(父子关系)和实体(树中的节点)的图数据库。要检索树,我们有两个单独的调用:getEdges和getEntitiesFromChildEdges。两者都归还期货。

初始调用getEquipment需要返回Future。由于我最终需要回归未来,我不想添加一个人为的等待。

这是代码。首先调用getEquipment

getTree( rootEntities: Seq[Entity], parents: Seq[Entity], depth: Int): Future[Seq[Entity]] {

  getEdges( parents).flatMap{ edges =>
    getEntitiesFromChildEdges( edges).map{ children =>
      children.foreach{ child => findParent( child).addChild( child) }
      if( depth > 0) {
        getTree( rootEntities, children, depth - 1)
        logTheTree( "DEEPER", rootEntities)
        rootEntities
      } else {
        logTheTree( "!DEEPER", rootEntities)
        rootEntities
      }
    }
  }

}


getEquipment: Future[Json]{

  getRootEntities.flatMap{ rootEntities =>
    getTree( rootEntities, rootEntities, 3).flatMap { returnedRootEntities =>
      logTheTree( "FINAL", rootEntities)
      Json.toJson( rootEntities)
    }
  }

}

首先调用getEquipment。这是日志结果。请注意,在完全构建树之前,FINAL的日志会返回。

DEEPER Root
DEEPER   One
FINAL Root
FINAL   One
---
DEEPER Root
DEEPER   One
DEEPER     Two
DEEPER     Three
!DEEPER Root
!DEEPER   One
!DEEPER     Two
!DEEPER     Three

树遍历是广度优先而不是深度优先,以最小化getEntities调用。与问题无关,但确实有助于理解树遍历传递序列的原因。

最后,我知道我不必要地传入rootEntities,但是当getTree.flatMap没有跟returnedRootEntities =>时,编译器会抱怨。实际getEquipment: Future更复杂,但这是针对不同的问题。

2 个答案:

答案 0 :(得分:3)

以下是基于我使用Promise处理此基于Future的递归的注释的示例。我简化了您的示例,因此代码更清晰,但仍然存在需要进行基于Future递归的相同问题。看一看,希望您可以将其与您的代码联系起来:

import concurrent.Future
import scala.concurrent.Promise
import concurrent.ExecutionContext.Implicits._
import scala.concurrent.Await
import concurrent.duration._

case class Person(id:Long, name:String, children:Option[List[Long]])
object RecurseTest extends App{
  val Everyone = Map(
    1L -> Person(1, "grandpa", Some(List(2,3))),
    2L -> Person(2, "dad", Some(List(4,5))),
    3L -> Person(3, "uncle joe", Some(List(6,7))),
    4L -> Person(4, "me", None),
    5L -> Person(5, "sis", None),
    6L -> Person(6, "cousin fred", None),
    7L -> Person(7, "cousin mary", None)
  )

  val f = getFamilMembers

  //Only using Await here to demonstrate the example.  Do not use in real code
  val result = Await.result(f, 5 seconds)
  println(result)

  def getFamilMembers():Future[List[String]] = {
    var names:List[String] = List.empty
    val prom = Promise[List[String]]
    def recurse(ids:List[Long]){
      ids.headOption match{

        //This is the recursion exit point
        case None => prom.success(names.reverse)
        case Some(id) =>
          getPerson(id) map{ p =>
            names = p.name :: names
            val childIds = p.children.getOrElse(List.empty)
            recurse(ids.tail ++ childIds)
          }                    
      }
    }

    recurse(List(1))  
    prom.future
  }

  def getPerson(id:Long):Future[Person] = {
    Future{         
      val p = Everyone.get(id).get
      println("loaded: " + p.name)
      p
    } 
  }
}

答案 1 :(得分:1)

有人指出代码提前返回的原因是因为最里面的getTree()立即返回一个Future(Doh!)。它应该有一个地图,并在地图内部包含它下面的两行。

else已更改为Future.successful,getEntitiesFromChildEdges( edges).map已更改为flatMap

这是更新的代码。

getTree( rootEntities: Seq[Entity], parents: Seq[Entity], depth: Int): Future[Seq[Entity]] {

  getEdges( parents).flatMap{ edges =>
    getEntitiesFromChildEdges( edges).flatMap{ children =>
      children.foreach{ child => findParent( child).addChild( child) }
      if( depth > 0) {
        // getTree needs to have a map and the results inside it.
        getTree( rootEntities, children, depth - 1).map{ r =>
          logTheTree( "DEEPER", rootEntities)
          rootEntities
        }
      } else {
        logTheTree( "!DEEPER", rootEntities)
        Future.successful( rootEntities)
      }
    }
  }

}

cmbaxter的回答显示了承诺:-)我将来会考虑使用这种技术。