本文主要讲解“Scala尾部递归的跟踪调用和限制方法是什么”,简单明了,易学易懂。请跟随边肖的思路一起学习和学习“Scala尾部递归的跟踪调用和限制方法是什么”!
如果你想把更新var的while循环变成只使用val的更实用的风格,有时你可以使用递归。以下示例是一个递归函数,它通过不断改进猜测数来逼近一个值:
函数如DEF Approximate(猜测: double): double=if(is good stow(猜测))猜测估计(improve(猜测)),在适当实现is good stow和improve的情况下,经常用于发现问题。如果希望近似函数执行得更快,您可能会尝试使用while循环来编写,以加快速度,例如:
defapproximatelop(initial guess : double): double={ varguess=initial guess while(!够好吗(猜)猜=提高(猜)猜}两个近似版本哪个更好?就简单性和避免var而言,* * *,函数类型胜出。但是有没有可能指令性方法会更有效呢?其实如果我们测量一下执行时间,就会发现它们几乎一模一样!这可能令人惊讶,因为递归调用似乎比简单地从循环的末尾跳到循环的开头花费的时间更长。
然而,在上面的近似例子中,Scala编译器可以应用一个重要的优化。请注意,递归调用是由近似函数体执行的* * *事情。像近似的,当他们* * *调用自己的函数时,就叫做尾递归。Scala编译器在检测到结束递归后,用新的值更新函数参数,然后用跳转回函数的开头来替换它。
在道德上,你不应该羞于使用递归算法来解决你的问题。递归通常是比基于循环更优雅和简洁的方案。如果该方案是尾部递归,则不需要支付任何运行时开销。
跟踪尾递归函数
尾部递归函数不会为每次调用创建新的堆栈帧;对的所有调用都将在一个框架中执行。这可能会让检查程序堆栈跟踪并失败的程序员感到惊讶。例如,此函数在多次调用自身后引发异常:
def boom(x : int): int=if(x==0)thrownew exception(' boom!')else BOM(x-1)1这个函数不是尾部递归,因为增量操作是在递归调用之后执行的。如果你执行它,你会得到你所期望的:
Scala BOM(3)Java . lang . exception : boom!At.boom(控制台:5) at.boom(控制台33606) at.boom(控制台:6) at.boom(控制台33606) at.init(控制台:6).如果修改boom now使其成为尾部递归:
defbang(x : int): int=if(x==0)thrownew exception(' bang!')elsebang(x1)您将获得:
scalabang(5)Java . lang . exception : bang!at.bang(控制台:5)
nbsp; at .< init>(< console>:6) ...
这回,你仅看到了bang的一个堆栈框架。或许你会认为bang在调用自己之前就崩溃了,但这不是事实。如果你认为你会在看到堆栈跟踪时被尾调用优化搞糊涂,你可以用开关项关掉它:
-g:notailcalls
把这个参数传给scala的shell或者scalac编译器。定义了这个选项,你就能得到一个长长的堆栈跟踪了:
scala> bang(5) java.lang.Exception: bang! at .bang(< console>:5) at .bang(< console>:5) at .bang(< console>:5) at .bang(< console>:5) at .bang(< console>:5) at .bang(< console>:5) at .< init>(< console>:6) ...
尾调用优化
approximate的编译后代码实质上与approximateLoop的编译后代码相同。两个函数编译后都是同样的事三个Java字节码指令。如果你看一下Scala编译器对尾递归方法,approximate,产生的字节码,你会看到尽管isGoodEnough和improve都被方法体调用,approximate却没有。Scala编译器优化了递归调用:
public double approximate(double); Code: 0: aload_0 1: astore_3 2: aload_0 3: dload_1 4: invokevirtual #24; //Method isGoodEnough:(D)Z 7: ifeq 12 10: dload_1 11: dreturn 12: aload_0 13: dload_1 14: invokevirtual #27; //Method improve:(D)D 17: dstore_1 18: goto 2
尾递归的局限
Scala里尾递归的使用局限很大,因为JVM指令集使实现更加先进的尾递归形式变得很困难。Scala仅优化了直接递归调用使其返回同一个函数。如果递归是间接的,就像在下面的例子里两个互相递归的函数,就没有优化的可能性了:
def isEven(x: Int): Boolean = if (x == 0) true else isOdd(x - 1) def isOdd(x: Int): Boolean = if (x == 0) false else isEven(x - 1)
同样如果***一个调用是一个函数值你也不能获得尾调用优化。请考虑下列递归代码的实例:
val funValue = nestedFun _ def nestedFun(x: Int) { if (x != 0) { println(x); funValue(x - 1) } }
funValue变量指向一个实质是包装了nestedFun的调用的函数值。当你把这个函数值应用到参数上,它会转向把nestedFun应用到同一个参数,并返回结果。因此你或许希望Scala编译器能执行尾调用优化,但在这个例子里做不到。因此,尾调用优化受限于方法或嵌套函数在***一个操作调用本身,而没有转到某个函数值或什么其它的中间函数的情况。
感谢各位的阅读,以上就是“Scala尾递归的跟踪调用及局限方法是什么”的内容了,经过本文的学习后,相信大家对Scala尾递归的跟踪调用及局限方法是什么这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!
内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/138729.html