score:56
Consider the following interaction with the REPL. First we define a class with a factorial method:
scala> class C {
def fact(n: Int, result: Int): Int =
if(n == 0) result
else fact(n - 1, n * result)
}
defined class C
scala> (new C).fact(5, 1)
res11: Int = 120
Now let's override it in a subclass to double the superclass's answer:
scala> class C2 extends C {
override def fact(n: Int, result: Int): Int = 2 * super.fact(n, result)
}
defined class C2
scala> (new C).fact(5, 1)
res12: Int = 120
scala> (new C2).fact(5, 1)
What result do you expect for this last call? You might be expecting 240. But no:
scala> (new C2).fact(5, 1)
res13: Int = 7680
That's because when the superclass's method makes a recursive call, the recursive call goes through the subclass.
If overriding worked such that 240 was the right answer, then it would be safe for tail-call optimization to be performed in the superclass here. But that isn't how Scala (or Java) works.
Unless a method is marked final, it might not be calling itself when it makes a recursive call.
And that's why @tailrec doesn't work unless a method is final (or private).
UPDATE: I recommend reading the other two answers (John's and Rex's) as well.
score:0
The popular and accepted answer to this question is actually misleading, because the question itself is confusing. The OP does not make the distinction between tailrec
and TCO
, and the answer does not address this.
The key point is that the requirements for tailrec
are more strict than the requirements for TCO
.
The tailrec
annotation requires that tail calls are made to the same function, whereas TCO
can be used on tail calls to any function.
The compiler could use TCO
on fact
because there is a call in the tail position. Specifically, it could turn the call to fact
into a jump to fact
by adjusting the stack appropriately. It does not matter that this version of fact
is not the same as the function making the call.
So the accepted answer correctly explains why a non-final function cannot be tailrec
because you cannot guarantee that the tail calls are to the same function and not to an overloaded version of the function. But it incorrectly implies that it is not safe to use TCO
on this method, when in fact this would be perfectly safe and a good optimisation.
[ Note that, as explained by Jon Harrop, you cannot implement TCO
on the JVM, but that is a restriction of the compiler, not the language, and is unrelated to tailrec
]
And for reference, here is how you can avoid the problem without making the method final
:
class C {
def fact(n: Int): Int = {
@tailrec
def loop(n: Int, result: Int): Int =
if (n == 0) {
result
} else {
loop(n - 1, n * result)
}
loop(n, 1)
}
}
This works because loop
is a concrete function rather than a method and cannot be overridden. This version also has the advantage of removing the spurious result
parameter to fact
.
This is the pattern I use for all recursive algorithms.
score:1
What exactly would go wrong if the compiler applied TCO in a case such as this?
Nothing would go wrong. Any language with proper tail call elimination will do this (SML, OCaml, F#, Haskell etc.). The only reason Scala does not is that the JVM does not support tail recursion and Scala's usual hack of replacing self-recursive calls in tail position with goto
does not work in this case. Scala on the CLR could do this as F# does.
score:7
Let foo::fact(n, res)
denote your routine. Let baz::fact(n, res)
denote someone else's override of your routine.
The compiler is telling you that the semantics allow baz::fact()
to be a wrapper, that MAY upcall (?) foo::fact()
if it wants to. Under such a scenario, the rule is that foo::fact()
, when it recurs, must activate baz::fact()
rather than foo::fact()
, and, while foo::fact()
is tail-recursive, baz::fact()
may not be. At that point, rather than looping on the tail-recursive call, foo::fact()
must return to baz::fact()
, so it can unwind itself.
score:23
Recursive calls might be to a subclass instead of to a superclass; final
will prevent that. But why might you want that behavior? The Fibonacci series doesn't provide any clues. But this does:
class Pretty {
def recursivePrinter(a: Any): String = { a match {
case xs: List[_] => xs.map(recursivePrinter).mkString("L[",",","]")
case xs: Array[_] => xs.map(recursivePrinter).mkString("A[",",","]")
case _ => a.toString
}}
}
class Prettier extends Pretty {
override def recursivePrinter(a: Any): String = { a match {
case s: Set[_] => s.map(recursivePrinter).mkString("{",",","}")
case _ => super.recursivePrinter(a)
}}
}
scala> (new Prettier).recursivePrinter(Set(Set(0,1),1))
res8: String = {{0,1},1}
If the Pretty call was tail-recursive, we'd print out {Set(0, 1),1}
instead since the extension wouldn't apply.
Since this sort of recursion is plausibly useful, and would be destroyed if tail calls on non-final methods were allowed, the compiler inserts a real call instead.
Source: stackoverflow.com
Related Query
- Why won't the Scala compiler apply tail call optimization unless a method is final?
- Why is it faster to call external scala compiler than use the runtime interpreter library?
- unapply method of a case class is not used by the Scala compiler to do pattern matching, why is that?
- Why do I need to call apply method explicity in a Scala Array obtained from java enum?
- Is there a way to tell the scala compiler not to do a tail call optimization?
- Why does scala call the method passed to println before printing the line it is called in?
- Scala Reflection to generate a companion object and call the apply method
- Why does the Scala compiler disallow overloaded methods with default arguments?
- Why does this explicit call of a Scala method allow it to be implicitly resolved?
- Why won't Scala optimize tail call with try/catch?
- Why scala uses reflection to call method on structural type?
- If the Nothing type is at the bottom of the class hierarchy, why can I not call any conceivable method on it?
- How do I call a Java method named the same as a Scala keyword?
- How to call main method of a Scala program from the main method of a java program?
- Why does Scala evaluate the argument for a call-by-name parameter if the method is infix and right-associative?
- Scala allowing call to java.util.HashMap get method with the wrong number of parameters
- Why does a large array constructor call break the Scala compiler?
- Why does Scala compiler for .NET ignore the meaning of val?
- Why does the Scala compiler say that copy is not a member of my case class?
- Why doesn't the scala compiler generate a warning on if statements that always yield false inside a pattern match?
- Call a method on the value of Scala Option if present
- why scala can't infer the type of method parameters
- Scala - the `apply()` without arguments method and curved braces on a call
- Why did the Scala compiler get more strict regarding self types in 2.10?
- How Scala Array apply method returns the value at the index when the implementation is simply throw new error
- Scala apply method call as parentheses conflicts with implicit parameters
- Why did the scala compiler not flag what appears to not be a tail-recursive function?
- Why should a Scala program have a main method or extend the App trait?
- Why does the Scala compiler fail with missing parameter type for filter with JavaSparkContext?
- Why the Scala method isInstanceOf[T] is not working
More Query from same tag
- Need help to refactor this scala method in functional programming style
- What is correct way to use operator orElse in Scala?
- Why is this specs2 test using Mockito passing?
- Infinispan write behind? What am i doing wrong
- Convert Dataframe with Vector column to Dataset - which type to be used in the case class
- What is the flow of execution in the following functions?
- How to get Scala Compiler Plugin to work in Scala IDE
- File processing from HDFS to Spark not working
- Build, using Shapeless, generic default instances for case classes with parameters defining a common createValue method
- Scala Macro: Define Top Level Object
- OWLAPI - how to add references like Declaration and cardinality?
- Use deployed artifacts instead of local project in sbt multi project build
- How to cast java.lang.Object to a specific type in Scala?
- Batch Insert in Lagom-Scala Cassandra readside
- Shadow any2stringadd when implicitly converting Symbol
- What's the relationship of the versions of scala when I use sbt to build a scala project?
- how to create vertx in scala
- mocking method inside another method scala
- Scala: reflection with different argument types
- Using Apache Flink to consume from a Kafka topic then processing the stream with Flink CEP
- Scala/Java : Compare two list/set and removed matched element from both list
- How to list all files from resources folder with scala
- Play Framework 2 / Redundant object validations
- Get the elements from different arraytype columns and build a column with heterogeneous data in Spark
- Forwarding calls to underlying object in Scala
- Syntax and meaning of a Scala/Play! code sample
- How to register a TypeSerializer with Flink?
- Implicit Conversion from tuple to custom type parameterized by size of tuple
- Jackson / Scala immutable case classes: parsing a class depending on other value in JSON
- How to implement the Seq.grouped(size:Int): Seq[Seq[A]] for Dataset in Spark