28

Say I got a recursive function that is tail recursive. I wonder if this function will be implemented as recursion, growing on the stack, or will it be changed to a loop (since it is a tail-recursive function)?

I have just read that Scala detects such calls and optimizes it but is this a Scala-only thing or JVM in general?

Raedwald
  • 46,613
  • 43
  • 151
  • 237
user3073853
  • 365
  • 3
  • 10
  • 14
    I'm pretty sure that this function is NOT tail-recursive. After nested call to sum(), there is still need to add something to the return value of that call. – Novakov Dec 29 '13 at 15:35
  • 6
    That's *not* a tail call. –  Dec 29 '13 at 15:35
  • 1
    Regardless, HotSpot does not support TCO: http://stackoverflow.com/q/3616483/139010 – Matt Ball Dec 29 '13 at 15:35
  • Why dont you do `new Throwable().printStackTrace();` at that begining of your method, and tell us what you see! – inquisitive Dec 29 '13 at 15:39
  • 1
    Creating a subList object is more expensive than the recursion (assuming you done run out of stack) Of course the most efficient option is plain iteration. – Peter Lawrey Dec 29 '13 at 16:03
  • 2
    Given that you are interested in this style of programming, is there a reason why you can't just use Scala? The equivalent of the above (the whole thing) would be `println((0 to 5).sum)`. If you consider using the sum() method cheating, you could generate the sum using a fold: `(0 /: (0 to 5))(_+_)`. As a method, a somewhat more general version of the above is `def sum( xs:Seq[Int] ):Int = if (xs.isEmpty) 0 else xs.head + sum(xs.tail)`. Or you could write a tail-recursive version and it would actually get optimized (by scalac). – AmigoNico Dec 30 '13 at 02:13

4 Answers4

20

Java supports tail-recursive calls, but AFAIK it doesn't optimize them away. I think it's the Scala compiler that is simply capable of this, not the JVM itself. Check out the @tailrec annotation in Scala to see what more the compiler is capable of :)

But regardless of whether Java/JVM optimizes tail-recursion away, your function would be harder to optimize than necessary.

Look at this:

int sum(List<Integer> integers) {
    return sum(integers, 0);
}

int sum(List<Integer> integers, int sumSoFar) {
    if (integers.isEmpty())
        return sumSoFar;
    else
        return sum(
                integers.subList(1, integers.size()),
                sumSoFar + integers.get(0)
        );
}

See, I've added an overloaded sum with a so-far calculated sum parameter. This way when you recur in the else branch you don't need the actual stack frame any more - you got all you need as function arguments in the recursive call.

In your snippet the stack frame would probably have to exist as long as the recursive call..

emesx
  • 12,555
  • 10
  • 58
  • 91
  • 6
    you probably know this, but anyways, `@tailrec` is used only to **enforce** optimized code -- if compiler is not able to do so, it will throw compilation error. But still, code will be optimized if it is possible to do, whether such annotation is presented or not. – om-nom-nom Dec 29 '13 at 19:28
  • 2
    @om-nom-nom yes, good that you pointed out this here. `@tailrec` in Scala is similar to `@Override` in Java (in terms of presence and compiler actions). – emesx Dec 29 '13 at 20:04
  • I believe this is called "Co-recursion" – SandeepGodara Apr 08 '19 at 18:51
9

Java and the JVM do not currently support tail calls

The fundamental work that needs to happen is at the level of the JVM, not Java. There has been a slow moving line of work to address this (initially as part of MLVM now under Project Loom). Despite being relatively old, this 2007 blog from John Rose is a good and short overview of how the JVM bytecode could be altered. I think that the tail call work was pushed aside in favor of finishing invokedynamic first (and that has uncovered more tricky design considerations for tail calls). Here is an excerpt from the latest Project Loom proposal:

As adding the ability to manipulate call stacks to the JVM will undoubtedly be required, it is also the goal of this project to add an even lighter-weight construct that will allow unwinding the stack to some point and then invoke a method with given arguments (basically, a generalization of efficient tail-calls). We will call that feature unwind-and-invoke, or UAI.


Some other details:

  • Java as a language is unlikely to ever automatically optimize tail-calls. This is because most implementations of tail-call elimination have somewhat unintuitive effects on the callstack (eg. if you throw an exception in a recursive call, you won't see all of the recursive calls in the stack trace).

    JavaScript is an example of a language that tried to automatically optimize tail-calls (in ES2015), and here is a debrief from the V8 team explaining the difficulties. They've since removed the feature and have switched to backing a proposal that supports tail-call optimization only when it is explicit.

    If the JVM ever adds support for tail-calls at the bytecode level, I speculate Java might also support explicit tail-call optimization (ie. in the form of an annotation on a return or an annotation on a function or maybe even a new keyword).

  • Scala tries to detect and optimize tail recursion into JVM bytecode loops. Here is a project that promises to perform this sort of optimization on existing JVM bytecode. The idea has not been adopted by Java because it is somewhat brittle and limited:

    1. mutually recursive functions get really tricky (Scala's @tailrec just gives up)
    2. polymorphic recursion can't work
    3. generalized tail calls obviously can't work
Alec
  • 31,829
  • 7
  • 67
  • 114
  • Its not clear how up to date some of the pages you link to are. – Raedwald May 27 '21 at 06:11
  • @Raedwald I spent a while researching this in the process of trying to figure out how to best compile WASM tail calls on the JVM. AFAICT, there really isn't anything much yet. Even the actual WIP is still pretty bare: https://github.com/openjdk/loom. Unlike other prioritized projects like Amber and Valhalla, Loom doesn't yet have a docs repo to talk about the design. It also doesn't have any draft JEPs: https://openjdk.java.net/jeps/0. I would be quite happy to see another answer proving me wrong though. – Alec May 27 '21 at 10:35
  • Scala's [`@tailrec` annotation](https://www.scala-lang.org/api/2.12.3/scala/annotation/tailrec.html) is a demand to the compiler that a function be optimized. It is not limited to doing so on only functions marked with that annotation. Scala is more than happy to go hunting for any `final` functions which can exhibit TCO. – Silvio Mayolo May 28 '21 at 00:30
  • @SilvioMayolo thanks for the reference! I had no idea this was the case. Do you know if there's a way to tell the Scala compiler to _not_ perform TCO? – Alec May 28 '21 at 00:50
  • To my knowledge, there's no way to suppress it. There could be some obscure annotation or compiler setting I'm not aware of, though – Silvio Mayolo May 28 '21 at 00:51
  • @SilvioMayolo Apparently there is `-g:notailcalls`, but I don't see a way to disable on a per function basis. – Alec May 28 '21 at 00:57
4

According to this article from April 2014:

It's important to note that this isn't a bug in the JVM. It's an optimization that can be implemented to help functional programmers who use recursion, which is much more common and normal in those languages. I recently spoke to Brian Goetz at Oracle about this optimization, and he said that it's on a list of things to be added to the JVM, but it's just not a high-priority item.

lbalazscs
  • 17,474
  • 7
  • 42
  • 50
-3

Tail recursion of Factorial in java, it has space complexity of O(1)

  int tailFactorial(int x){
    return tailFactorial(x, 1);
}

int tailFactorial(int x, int totalFactor){
    if(x == 0){
        return totalFactor;
    }else{
        return tailFactorial(x-1, totalFactor * x);
    }

}
loakesh bachhu
  • 323
  • 3
  • 4