如何在scala中实现尾递归函数,将future作为返回值:
示例代码
def getInfo(lists: List[Int]): Future[List[Int]] = {
def getStudentIDs(lists: List[Int]): Future[List[Int]] = {
//here a web service call that returns a future WS response
val response=ws.call(someURL)
response.map(r => {
r.status match {
case 200 =>
var newList = lists + (r.json \ ids)
.as[List[Int]] //here add the ws call response json..
getStudentIDs(newList)
case 429 =>Future.sucessful(lists)
case _ => getStudentIDs(lists)
}
})
}
getStudentIDs(List.empty[Int])
}
答案 0 :(得分:0)
这是问题(简化了使其可运行的代码):
import scala.concurrent._
import scala.concurrent.duration._
import annotation.tailrec
import scala.concurrent.ExecutionContext.Implicits.global
def getInfo(lists: List[Int]): Future[List[Int]] = {
@tailrec
def getStudentIDs(lists: List[Int]): Future[List[Int]] = {
Future(List(1, 2, 3)).flatMap(x => getStudentIDs(x ::: lists))
}
getStudentIDs(List.empty[Int])
}
给出错误:
error: could not optimize @tailrec annotated method getStudentIDs:
it contains a recursive call not in tail position
Future(1).flatMap(x => getStudentIDs(x :: lists))
^
问题不仅在于Future
。实际问题是getStudents
不在终端/尾部位置 - 它是从map
调用的。如果您不使用Futures并使用常规map
来自集合或任何其他函数,那么这将是一个问题。例如:
def getInfo(lists: List[Int]): List[Int] = {
@tailrec
def getStudentIDs(lists: List[Int]): List[Int] = {
List(1).flatMap(x => getStudentIDs(x :: lists))
}
getStudentIDs(List.empty[Int])
}
给出了同样的错误:
error: could not optimize @tailrec annotated method getStudentIDs:
it contains a recursive call not in tail position
List(1).flatMap(x => getStudentIDs(x :: lists))
^
这让您感到困难的是,您可以直接从未来获得结果,以便在getStudents
中使用它,因为您不知道它是否已经完成并且#&#39} 39;阻止未来并等待结果不是一个好习惯。所以你有点被迫使用map
。这是一个非常糟糕的例子,说明如何使尾递归(仅适用于科学:))。不要在生产代码中这样做:
def getInfo(lists: List[Int]): Future[List[Int]] = {
@tailrec
def getStudentIDs(lists: List[Int]): Future[List[Int]] = {
val r = Await.ready(Future(List(1, 2, 3)), Duration.Inf).value.get.getOrElse(lists)
getStudentIDs(r ::: lists)
}
getStudentIDs(List.empty[Int])
}
编译器很高兴,但这可能会导致许多问题 - 请阅读Await
,blocking
和线程池以获取更多信息。
我认为你的函数不是尾递归可能不是一个大问题,因为你可能不想以这种方式创造很多未来。如果它真的像演员(Akka)等问题那么你可以尝试其他并发框架。
答案 1 :(得分:0)
我认为这是一个XY问题。你可能不想要它"尾递归"在"具有@tailrec" -annotation的意义上。你想要的是堆栈安全性,这样这种方法在几百次重试之后就不会将堆栈连接到你的web服务。
为此,有一些库,例如Cats。
在Cats中,有一个名为Monad
的类型类,这个类型类为你想要的东西提供了一种特殊的方法:
tailRecM[A, B](a: A)(f: (A) => F[Either[A, B]]): F[B]
来自文档的引用:
保持调用f直到返回scala.util.Right [B]。 此方法的实现应使用常量堆栈空间[...]
Future
in FutureInstances
可以实现此功能。但实现似乎微不足道,因为它混合在StackSafeMonad
。
您当然可以查看StackSafeMonad
的实现,然后尝试理解为什么它在Future
的情况下就足够了,但您也可以只使用库实现而不用担心您的递归方法是否会因StackOverflowError
而失败。