首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

@tailrec为什么这个方法没有编译成“包含一个递归调用不在尾部位置”?

@tailrec是Scala语言中的一个注解,用于标记一个方法是否是尾递归的。尾递归是指递归调用在方法的最后一步执行,并且递归调用的返回值直接被当前方法返回,不再进行其他操作。

如果一个方法被标记为@tailrec,编译器会对该方法进行尾递归优化。优化后的方法会被编译成循环而不是递归调用,从而避免了递归调用可能导致的栈溢出问题。

然而,有时候编译器无法将一个方法编译成尾递归形式,原因可能是:

  1. 递归调用不在方法的最后一步执行:如果递归调用后还有其他操作,例如对返回值进行处理或者进行其他计算,那么编译器无法将其优化为尾递归形式。
  2. 递归调用的返回值需要进行进一步的操作:如果递归调用的返回值需要进行其他操作,例如与其他变量相加或者进行其他运算,那么编译器无法将其优化为尾递归形式。
  3. 方法中存在多个递归调用:如果方法中存在多个递归调用,编译器无法确定哪个递归调用应该被优化为尾递归形式。

总之,@tailrec注解只是一个提示,告诉编译器该方法应该是尾递归的。但是最终是否能够成功优化为尾递归形式,还取决于方法的具体实现。如果方法的递归调用不满足尾递归的条件,编译器就无法进行优化。

页面内容是否对你有帮助?
有帮助
没帮助

相关·内容

ES6中的尾调用优化

粗略的来说,如果当一个函数所做的最后一件事是调用了另一个函数,而后者不需要向调用者返回任何东西时;以及由此可知,在这种情况下没有调用者的额外信息需要被储存在调用栈(call stack)上,函数间的调用更像一种...id()返回了数值3,或者可以说它为f()返回了这个值;因为通过行C,该值被传递给了f的调用者。 不难发现,行B的函数调用就是一个调用。这样的调用可以在栈0增长的情况下完成。...尾递归函数 如果一个函数的主递归调用发生在尾部,那这个函数就是尾递归。...譬如,下面的阶乘函数不是尾递归,因为行A中的主递归调用不在尾部: function factorial(x) { if (x <= 0) { return 1; } else...{ return x * factorial(x-1); // (A) } } 可以用一个辅助方法facRec()来使factorial()成为尾递归

93620
  • Kotlin入门(11)江湖绝技之特殊函数

    ,也就是在类外面定义,不在类内部定义。...此时要在fun前面加上关键字tailrec,告诉编译器这是个尾递归函数,则编译器会相应进行优化,从而提高程序性能。...以下是个尾递归函数的声明代码例子: //如果函数尾部递归调用自身,则可加上关键字tailrec表示这是个尾递归函数, //此时编译器会自动优化递归,即用循环方式代替递归,从而避免栈溢出的情况。...//比如下面这个求余弦不动点的函数就是尾递归函数 tailrec fun findFixPoint(x: Double = 1.0): Double = if (x == Math.cos...为了解释地更加清楚些,我们来看一个例子。对于一个数组对象,若想求得该数组元素的最大值,可以调用数组对象的max方法

    1.2K10

    Kotlin学习笔记(五)-常见高阶函数

    这允许一些通常用循环写的算法改用递归函数来写,而无堆栈溢出的⻛险。当一个函数用tailrec修饰符标记并满足所需的形式时,编译器会优化该递归,留下一个快速而高效的基于循环的版本。 这是官网的说法。...: findTreeNode(root.right, value) } 调用完自己之后没有任何操作的递归就是尾递归递归优化就是在方法_上加tailrec关键地提示编译器进行优化(将递归转化味迭代进行处理...) 若非尾递归加上tailrec也会提示(提示黄色警告)。...在Java中调用方法方法内部的状态是不会被记住的,但是在Kotlin中,函数的状态在调用后不会被销毁。...那么假设有一个方法需要传10个参数,可能A模块传了2个,然后返回函数,B模块调用A模块的方法并将其8个参数补齐,并真正使用。

    85720

    大家都知道递归,尾递归呢?什么又是尾递归优化?

    为什么会有“栈溢出”呢?因为函数调用的过程,都要借助“栈”这种存储结构来保存运行时的一些状态,比如函数调用过程中的变量拷贝,函数调用的地址等等。...为什么呢?因为这种写法,本质上还是有多层的函数嵌套调用,中间仍然有压栈、出栈等占用了存储空间(只不过能比前面的方法会省部分空间)。...(好像 Java 的编译器没做这方面的优化,至少我实验我本地 JDK8 是没有的,不清楚最新版本的有木有)(scala 本身提供了一个注解帮助编译器强制校验是否能够进行尾递归优化@tailrec) object...默认启用尾递归优化正常计算结果,禁用尾递归优化则“StackOverflow”。 我们来看看生成的字节码有什么不同。 ? 包含递归优化的字节码,直接 goto 循环。 ?...禁用尾递归优化的字节码,方法调用。 从上面可以看出,尾递归优化后,变成循环了(前面的 C++ 类似)。 好了,尾递归咱们就了解到这里。

    1.5K30

    《Kotin 极简教程》第8章 函数式编程(FP)(2)

    函数声明 Kotlin 中的函数使用 fun 关键字声明 fun double(x: Int): Int { return 2*x } 函数用法 调用函数使用传统的方法 fun test() {...这都没有问题,但是调用处不是很优雅: treeNode.findParentOfType(MyTreeNode::class.java) 我们真正想要的只是传一个类型给该函数,即像这样调用它: treeNode.findParentOfType...8.2.10 尾递归tailrec Kotlin 支持一种称为尾递归的函数式编程风格。 这允许一些通常用循环写的算法改用递归函数来写,而无堆栈溢出的风险。...当一个函数用 tailrec 修饰符标记并满足所需的形式时,编译器会优化该递归,生成一个快速而高效的基于循环的版本。...在递归调用后有更多代码时,不能使用尾递归,并且不能用在 try/catch/finally 块中。尾部递归在 JVM 后端中支持。 Kotlin 还为集合类引入了许多扩展函数。

    1.8K20

    Kotlin之基本语法

    如果没有指定包名,那这个文件的内容就从属于一个默认的 “default” 包。 Imports 在源文件中,除了模块中默认导入的包,每个文件也可以有它自己的导入指令。...= double(2) 如果在其他类需要调用调用成员函数: Sample().foo() // 创建Sample类的实例,调用foo方法 中缀符号 在满足如下条件时:它们是成员函数或者是扩展函数,只有一个参数...比如: //给 Int 定义一个扩展方法 infix fun Int.shl(x: Int): Int { ... } 1 shl 2 //用中缀注解调用扩展函数 1.shl(2) Unit类型 如果函数不会返回任何有用值...Kotlin 支持局部函数,比如在一个函数包含另一函数。...这个允许一些算法可以通过循环而不是递归解决问题,从而避免了栈溢出。当函数被标记为 tailrec 时,编译器会优化递归,并用高效迅速的循环代替它。

    1.1K70

    Kotlin之基本语法

    如果没有指定包名,那这个文件的内容就从属于一个默认的 “default” 包。 Imports 在源文件中,除了模块中默认导入的包,每个文件也可以有它自己的导入指令。...= double(2) 如果在其他类需要调用调用成员函数: Sample().foo() // 创建Sample类的实例,调用foo方法 中缀符号 在满足如下条件时:它们是成员函数或者是扩展函数,只有一个参数...比如: //给 Int 定义一个扩展方法 infix fun Int.shl(x: Int): Int { ... } 1 shl 2 //用中缀注解调用扩展函数 1.shl(2) Unit类型 如果函数不会返回任何有用值...Kotlin 支持局部函数,比如在一个函数包含另一函数。...这个允许一些算法可以通过循环而不是递归解决问题,从而避免了栈溢出。当函数被标记为 tailrec 时,编译器会优化递归,并用高效迅速的循环代替它。

    1.1K80

    JavaScript 中的尾调用和优化

    递归 顾名思义,在一个调用中,如果函数最后的尾调用位置上是这个函数本身,则被称为尾递归递归很常用,但如果没写好的话也会非常消耗内存,导致爆栈。...尾递归优化 改写为循环 之所以需要优化,是因为调用栈过多,那么只要避免了函数内部的递归调用就可以解决掉这个问题,其中一个方法是用循环代替递归。...逻辑运算符(|| 与 &&) 首先是 || 运算符: const a = () => f() || g() 这里 f 函数不在递归位置上,而 g 函数在尾递归位置上,为什么,把函数改写一下就清楚了:...result) {    return result  } else {    return g()  }} 说明 f 函数也不在递归位置上,而 g 函数在尾递归位置上 逗号运算符(,) const...基于以上原因,V8 团队建议使用特殊的语法来指定尾递归优化,TC39 标准委员会有一个没有结论的提案叫做从语法上指定尾部调行为,这个提案由来自 Mozilla 和微软的委员提出。

    1.1K10

    Scala第三章学习笔记

    class FooHolder { def foo() { println("foo was called") } } Scala认为def foo()这行代码定义了一个抽象方法...这是因为它没有捕捉到后面的大括号,认定def foo()是完整的一行语句。当编译时,它认为这是一个洗呢匿名代码块,应该在类构建过程中执行。...注解优化 @tailrec @switch注解 使用@switch注解 ? 编译器给出了警告语句。因为模式匹配无法优化,编译不过。...@tailre 注解用于确保可以对方法执行尾递归优化,是把最后一句语句调用自身的函数转换为不占用栈控件,而是类似传统的while或for循环那样执行。...要优化尾递归调用,Scala编译器需要以下条件。 (1)方法必须是final或私有。方法不能多态。 (2)方法必须注明返回类型。 (3)方法必须在其某个分支的最后一句调用自身。

    44210

    一篇就够——Kotlin快速入门

    这个视频的名字虽然有进阶字样,可我们能看到的视频部分真的没有进阶内容,余下的,大概就需要去购买了吧。不过,老话说的好:师傅领进门,修行靠个人。这部分内容虽然没有进阶,但是足以将我们领进门了。...尾递归 :是指某个函数的最后一步依旧是调用自身 kotlin中尾递归关键字 tailrec 参考:阮一峰老师关于尾递归的介绍 http://www.ruanyifeng.com/blog/2015/04.../tail-call.html 2、为什么需要尾递归优化?...递归非常耗费内存,因为需要同时保存成千上百个调用记录,很容易发生"栈溢出"错误(stack overflow)。但 对于尾递归来说,由于只存在一个调用记录,所以永远不会发生"栈溢出"错误。...——为类定义成员方法 //定义一个类,包含两个成员变量 height和width.并定义一个成员方法 class Rect(var height: Int, var width: Int) {

    1.7K20

    Java初学者的30个常见问题

    后者是JAVA推荐的方法,因为它的写法 int[] 更能表明这是一个 int 的数组。 Q. 为什么数组下标从0 开始 而不是从 1 开始? A. 这种传统起源于机器语言的编程方法。...那为什么不把所有的参数都使用传值的方式,包括对待数组? A. 但数组很大时,复制数组需要大量的性能开销。因为这个原因,绝大多数变成语言支持把数组传入函数但不复制一个副本——MATLAB语言除外。...2.3 递归调用 Q. 有没有只能用循环而不能用递归的情况? A. 不可能,所有的循环都可以用递归替代,虽然大多数情况下,递归需要额外的内存。 Q. 有没有只能用递归而不能用循环的情况? A....JAVA对于栈的实现就是一个典型的宽接口的例子。 Q. 我想使用数组来表示一个包含泛型的栈,但是以下代码编译报错。为什么? A. 不错的尝试。不幸的是,创建一个泛型数组在 Java 1.5里不支持。...编译器在翻译时,可能把那种“尾递归”形式翻译成等价的循环形式。所以可能并没有可以被观测到的性能提升。 尾部递归是一种编程技巧。如果在递归函数中,递归调用返回的结果总被直接返回,则称为尾部递归

    1.8K51

    Kotlin 1.4 新特性预览

    得益于新的类型推导算法,之前一直只有调用接收 Java 单一方法接口的 Java 的方法时才可以有 SAM 转换,现在这个问题不存在了,且看例子: //注意 fun interface 是新特性 fun...2.4 带有默认参数的函数的类型支持 如果一个函数有默认参数,我们在调用它的时候就可以不传入这个参数了,例如: fun foo(i: Int = 0): String = "$i!"...这里还有一个细节,如果一个类当中同时存在用到和没用到 KProperty 对象的两类属性代理,那么生成的数组在 1.4 当中只包含用到的 KProperty 对象,例如: class MyOtherClass...那有可能你没有用到过多行编辑: ? 图 7:多行编辑逗号的问题 这里这个逗号有时候会特别碍事儿,但如何每一行都可以有一个逗号这个问题就简单多了: ?...,即函数的最后一个操作必须只能是调用自己,父类的函数声明为 tailrec 并不能保证子类能够正确地按要求覆写,于是产生矛盾。

    98210

    2020年大厂敲门砖--巧刷算法题

    然而主动权不在我们。 包括我,应该有不少同学在看算法题的时候不知道该从和下手,万物皆可盘,今天就来盘下常见的那些敲门砖。...一般可以用链表+hashMap的方式来实现:当查询时缓存中有,则从链表中找到该值,并移动到头部(从原位置删除并插入头部);如果缓存中没有则考虑插入,如果空间未满,则直接插入到头部,如果空间已满,则需要将尾部的元素删除...说实话这道题至今还是没有想到一个特别优的解法,也在脉脉放出来和大家交流了下,无非是用N个指针初始化到每个数组的第0位,然后通过计算均值,来选择离均值最远的一个进行移动,和算K-means的那种感觉似的,...回溯,其实就是一个带现场维护的一个递归(记录现场,递归,恢复现场),所以,我们可以从这个经典问题中看到什么? 全是套路! ? 把8皇后的解法抽象一下,一个解决递归类问题的模板出来了。。。...n + 1]; //dp[0] 是最初的状态,可以当成终止条件或基础条件 dp[0] = 1; //状态转移方式,当前的方法数,依赖于不选当前硬币的前一个状态 dp[k] += dp[k - coin

    38820

    Swift 派发机制

    运行时会根据这一个表去决定实际要被调用的函数; 一个函数被调用时会先去读取对象的函数表(读取第一次),再根据类的地址加上该的函数的偏移量得到函数地址(读取第二次),最后跳到那个地址上去(跳转一次)。...内联主要原理是:将一些函数的实现直接编译入调用函数的位置中去,减少函数指针的栈调用,提高运行效率。...下列情况编译器默认不会进行内联优化: 函数体过长(无形中增加了包体积,重复代码); 函数包含动态派发; 函数中包含递归调用; Swift 中显式内联优化修饰符 @inline(never) 声明这个函数...never 永远不被编译成 inline 的形式,即使开启了编译器优化; @inline(__always) 声明这个函数总是编译成 inline 的形式, 这个修饰符只对函数体过长这种不会被内联优化的情况生效...比如一个函数没有 override,Swift 就可能会使用直接派发的方式。

    1.1K20

    Scala的基础概念

    ,把计算当做数学函数求值的过程,并且避免了改变状态和可变的数据 纯函数特点 Pure Function 纯函数,没有副作用的函数 没有副作用:状态的变化 例如:调用 def Add(y:Int...) = x + y 其结果为xy之和,并且调用之后没有引起x值的变换,没有副作用 所以,Add函数没有副作用 引用透明性 对于上述Add函数,对于同一输入y,返回结果均相同 所以,Add具有引用透明性...递归函数 函数式编程中没有循环语句,全部的循环用递归实现 调优递归:尾递归 函数式编程的优点 Lisp是第一种函数式编程语言 编程代码量少 当构造完含数之后,对于相同输入,输出相同,便于调试 非常适用于并行编程...输出:死循环 进行函数设计和调用时,两种差异要搞清楚 Scala中的函数 支持把函数作为实参传递给另外一个函数 支持把函数作为返回值 支持把函数赋值给变量 支持把函数存储在数据结构里 即,在scala中...,尾递归会复写当前栈,不会导致堆栈溢出 尾递归优化:用¥annotation.tailrec显示指明编译时进行尾递归优化 @annotation.tailrec def factorial(n: Int

    73830

    面试官:说一说递归如何优化-尾递归优化

    情况二也属于调用后还有操作,即使写在一行内。 ❝尾调用不一定出现在函数尾部,只要是最后一步操作即可。...❝调用记录:可以理解为函数在哪个点执行的 我们知道,函数调用会在内存形成一个"调用记录",又称"调用帧"(call frame),保存调用位置和内部变量等信息。...如果按照阮一峰老师讲解完,大家还是没有太理解的话,我把我个人的理解说一下: 假如使用了尾递归优化,在执行到最后一行的时候,其实就可以看成,就是这一个函数mutiply(n-1, n * total)在执行...这样做的缺点就是不太直观,第一眼很难看出来,为什么计算5的阶乘,需要传入两个参数5和1? 两个方法可以解决这个问题。 方法一:是在尾递归函数之外,再提供一个正常形式的函数。...总结一下,递归本质上是一种循环操作。纯粹的函数式编程语言没有循环操作命令,所有的循环都用递归实现,这就是为什么递归对这些语言极其重要。

    3.8K22

    面试被问尾递归优化知道怎么做吗?

    什么是尾递归呢? 调用者在调用一个递归函数并取得返回值之后,不在进行其它计算,直接返回!有什么好处呢?...常规的函数调用总是会在调用栈最上层添加一个新的堆栈帧(stack frame,也翻译为“栈帧”或简称为“帧”),这个过程被称作“入栈”或“压栈”(意即把新的帧压在栈顶)。...[1]其中,对尾递归情形的优化效果最为明显,尤其是递归算法非常复杂的情形。—— 维基百科” 看完这些概念会很晦涩,还是难以理解,下面让我们通过一个简单的阶乘例子彻底弄清楚它。...= 1 * 2 * 3 * (n -1)n 普通的递归调用 下面这个例子中,拿到尾部 factorial() 返回值之后没有直接返回,而是又做了一次乘法运算,那么这就不是一个递归。...1) { return total; } return factorial(n - 1, total * n); // 重点在尾部调用返回 } console.log

    1.2K40

    面试被问尾递归优化知道怎么做吗?

    什么是尾递归呢? 调用者在调用一个递归函数并取得返回值之后,不在进行其它计算,直接返回!有什么好处呢?...常规的函数调用总是会在调用栈最上层添加一个新的堆栈帧(stack frame,也翻译为“栈帧”或简称为“帧”),这个过程被称作“入栈”或“压栈”(意即把新的帧压在栈顶)。...[1]其中,对尾递归情形的优化效果最为明显,尤其是递归算法非常复杂的情形。—— 维基百科” 看完这些概念会很晦涩,还是难以理解,下面让我们通过一个简单的阶乘例子彻底弄清楚它。...= 1 * 2 * 3 * (n -1)n 普通的递归调用 下面这个例子中,拿到尾部 factorial() 返回值之后没有直接返回,而是又做了一次乘法运算,那么这就不是一个递归。...1) { return total; } return factorial(n - 1, total * n); // 重点在尾部调用返回 } console.log

    48210

    如何优化尾调用

    需要了解如何优化尾递归的话,我们需要从最开始讲起。 什么是尾调用 什么是尾递归 如何优化尾递归调用 从字面理解,自然而言就是在函数的尾部返回一个函数的调用,通常来说,指的是函数执行的最后一步。...const fn = () => f1() || f2() // 这里的话, f2函数有可能是尾调用,f1不可能是尾调用 为什么f1函数不是呢,我们看这个函数的等价形式?...---- 说到这里,为什么要说尾调用呢?我们事先想一想传统的递归,典型的就是首先执行递归调用,然后根据这个递归的返回值并结算结果,那么传统的递归缺点有哪些呢? 效率低,占内存。...“按照阮一峰老师在es6的函数扩展中的解释就是:函数调用会在内存形成一个调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。...// 伪代码 F[i] = F[i-1] + F[i-2] 嗯,将一个递归函数转换成循环迭代函数,算是手动优化一种方式,在我们语言没有原生支持尾递归优化,那么可以考虑这种情况。

    90430
    领券