Scala 如何使一个函数具有递归性?

Scala 如何使一个函数具有递归性?,scala,dictionary,recursion,future,tail,Scala,Dictionary,Recursion,Future,Tail,在我的Scala应用程序中,我有一个函数,它调用一个返回Future[T]类型结果的函数。我需要在递归函数调用中传递映射结果。我希望这是尾部递归的,但是map(或flatMap)正在破坏这样做的能力。我得到一个错误“递归调用不在尾部位置。” 下面是这个场景的一个简单示例。如何修改它以使调用是尾部递归的(而不破坏使用Await.result()的未来的好处) import scala.annotation.tailrec 导入scala.concurrent.{wait,Future} 导入sca

在我的Scala应用程序中,我有一个函数,它调用一个返回Future[T]类型结果的函数。我需要在递归函数调用中传递映射结果。我希望这是尾部递归的,但是map(或flatMap)正在破坏这样做的能力。我得到一个错误“递归调用不在尾部位置。”

下面是这个场景的一个简单示例。如何修改它以使调用是尾部递归的(而不破坏使用Await.result()的未来的好处)

import scala.annotation.tailrec
导入scala.concurrent.{wait,Future}
导入scala.concurrent.duration_
隐式val ec=scala.concurrent.ExecutionContext.global
对象阶乘{
def阶乘(n:Int):未来[Int]={
@泰勒克
def factorialAcc(acc:Int,n:Int):未来[Int]={
如果(n个因数ALACC(num*acc,num-1))
}
}
factorialAcc(1,n)
}
受保护的def getFutureNumber(n:Int):Future[Int]=Future.successful(n)
}
等待结果(FactorialCalc.factorial(4),5秒)

Make
factorialAcc
返回一个Int,并且只在以后的
factorial
函数中包装它

def factorial(n: Int): Future[Int] = {

    @tailrec
    def factorialAcc(acc: Int, n: Int): Int = {
      if (n <= 1) {
        acc
      } else {
        factorialAcc(n*acc,n-1)
      }
    }

    future {
      factorialAcc(1, n)
    }
}
定义阶乘(n:Int):未来[Int]={ @泰勒克 def factorialAcc(acc:Int,n:Int):Int={
if(nMake
factorialAcc
返回一个Int,并且只在以后的
factorial
函数中包装它

def factorial(n: Int): Future[Int] = {

    @tailrec
    def factorialAcc(acc: Int, n: Int): Int = {
      if (n <= 1) {
        acc
      } else {
        factorialAcc(n*acc,n-1)
      }
    }

    future {
      factorialAcc(1, n)
    }
}
定义阶乘(n:Int):未来[Int]={ @泰勒克 def factorialAcc(acc:Int,n:Int):Int={
如果(n用foldLeft代替怎么样

def factorial(n: Int): Future[Int] = future {
  (1 to n).foldLeft(1) { _ * _ }
}

改用foldLeft怎么样

def factorial(n: Int): Future[Int] = future {
  (1 to n).foldLeft(1) { _ * _ }
}

我可能弄错了,但在这种情况下,您的函数不需要是尾部递归的

尾部递归帮助我们在使用递归函数的情况下不使用堆栈。但是,在您的情况下,我们实际上并没有像典型递归函数那样使用堆栈

这是因为“递归”调用将在执行上下文中的某个线程上异步发生。因此,此递归调用很可能不会与第一个调用驻留在同一堆栈上

factorialAcc
方法将创建future对象,该对象最终将异步触发“递归”调用。之后,它将立即从堆栈中弹出

所以这实际上不是堆栈递归,堆栈的增长不与n成正比,它大致保持不变的大小

您可以通过在
factorialAcc
方法中的某个点抛出异常并检查堆栈跟踪来轻松检查这一点

我重写了您的程序以获得更可读的堆栈跟踪:

object Main extends App {
  import scala.concurrent.{Await, Future}
  import scala.concurrent.duration._

  implicit val ec = scala.concurrent.ExecutionContext.global

  def factorialAcc(acc: Int, n: Int): Future[Int] = {

    if (n == 97)
      throw new Exception("n is 97")

    if (n <= 1) {
      Future.successful(acc)

    } else {
      val fNum = getFutureNumber(n)
      fNum.flatMap(num => factorialAcc(num * acc, num - 1))
    }
  }


  def factorial(n: Int): Future[Int] = {
      factorialAcc(1, n)
  }

  protected def getFutureNumber(n: Int) : Future[Int] = Future.successful(n)

  val r = Await.result(factorial(100), 5.seconds)
  println(r)

}

因此,您可以看到堆栈实际上很短。如果这是堆栈递归,您应该看到大约97个对
factorialAcc
方法的调用。相反,您只看到一个。

我可能错了,但在这种情况下,您的函数不需要是尾部递归的

尾部递归帮助我们在使用递归函数的情况下不使用堆栈。但是,在您的情况下,我们实际上并没有像典型递归函数那样使用堆栈

这是因为“递归”调用将在执行上下文中的某个线程上异步发生。因此,此递归调用很可能不会与第一个调用驻留在同一堆栈上

factorialAcc
方法将创建future对象,该对象最终将异步触发“递归”调用。之后,它将立即从堆栈中弹出

所以这实际上不是堆栈递归,堆栈的增长不与n成正比,它大致保持不变的大小

您可以通过在
factorialAcc
方法中的某个点抛出异常并检查堆栈跟踪来轻松检查这一点

我重写了您的程序以获得更可读的堆栈跟踪:

object Main extends App {
  import scala.concurrent.{Await, Future}
  import scala.concurrent.duration._

  implicit val ec = scala.concurrent.ExecutionContext.global

  def factorialAcc(acc: Int, n: Int): Future[Int] = {

    if (n == 97)
      throw new Exception("n is 97")

    if (n <= 1) {
      Future.successful(acc)

    } else {
      val fNum = getFutureNumber(n)
      fNum.flatMap(num => factorialAcc(num * acc, num - 1))
    }
  }


  def factorial(n: Int): Future[Int] = {
      factorialAcc(1, n)
  }

  protected def getFutureNumber(n: Int) : Future[Int] = Future.successful(n)

  val r = Await.result(factorial(100), 5.seconds)
  println(r)

}

因此,您可以看到堆栈实际上很短。如果这是堆栈递归,您应该看到大约97个对
factorialAcc
方法的调用。相反,您只看到一个。

这里有一个foldLeft解决方案,它调用另一个返回未来的函数

def factorial(n: Int): Future[Int] =
  (1 to n).foldLeft(Future.successful(1)) {
    (f, n) => f.flatMap(a => getFutureNumber(n).map(b => a * b))
  }

def getFutureNumber(n: Int) : Future[Int] = Future.successful(n)

这里有一个foldLeft解决方案,它调用另一个返回未来的函数

def factorial(n: Int): Future[Int] =
  (1 to n).foldLeft(Future.successful(1)) {
    (f, n) => f.flatMap(a => getFutureNumber(n).map(b => a * b))
  }

def getFutureNumber(n: Int) : Future[Int] = Future.successful(n)

这忽略了问题的关键..虽然该示例确实适用于此问题的尾部递归解决方案..但真正的问题是函数本身需要调用另一个返回未来的函数。因此,这在这里不起作用。这忽略了问题的关键..而该示例适用于此问题的尾部递归解决方案p问题..真正的问题是函数本身需要调用另一个返回未来的函数。所以这在这里不起作用。函数本身需要调用另一个返回未来的函数。所以这在这里不起作用。马吕斯的答案是正确的。函数本身需要调用另一个返回未来的函数。所以在这里行不通。马吕斯的答案是正确的。我认为这个答案适合我所需要的,但是仍然有一点内存泄漏,因为scala.concurrent Futures不会被合并。但是,twitter Futures是通过某种巫术实现的。也就是说,在崩溃之前你必须深入研究。比实际情况更深,所以我“我对此很满意。我正在使用Play的WS-client库调查类似的问题。您对这个内存泄漏问题有更多的了解吗?堆栈如何保持大致不变的大小?堆栈的总体大小是否会被吞噬,而不仅仅是分布在不同的线程中?我认为这个答案适合我的需要,但是存在一些问题。”仍然是一个轻微的内存泄漏,因为scala.concurrent Futures不会被合并。但是,twitter Futures是通过某种巫术实现的。也就是说,在崩溃之前,你必须深入研究。比实际情况更深入,所以我对此感到高兴。我正在使用Play的WS-clie调查类似的事情