Considere la siguiente interacción con REPL. Primero definimos una clase con un método factorial:
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
Ahora vamos a anularlo en una subclase de duplicar la respuesta de la superclase:
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)
¿Qué resultado se puede esperar de esta última llamada? Usted podría estar esperando 240. Pero no:
scala> (new C2).fact(5, 1)
res13: Int = 7680
Eso es porque cuando el método de la superclase hace una llamada recursiva, la llamada recursiva pasa a través de la subclase.
Si la anulación funcionaba de forma tal que 240 era la respuesta correcta, entonces sería seguro realizar una optimización de la cola de llamada en la superclase aquí. Pero no es así como funciona Scala (o Java).
A menos que un método se marque como final, es posible que no se llame a sí mismo cuando realiza una llamada recursiva.
Y es por eso que @tailrec no funciona a menos que un método sea final (o privado).
ACTUALIZACIÓN: Recomiendo leer las otras dos respuestas (de John y Rex) también.
gracias amacleod, drdozer, y otros en #scala –
¡Gran explicación! – soc
Puede valer la pena deletrear que "podría no llamarse a sí mismo" es un problema específico de Scala que no tiene nada que ver con la eliminación de llamadas finales en general. Todas estas llamadas finales se eliminarían en SML, OCaml, F #, etc. –