Didn't know there's a flag.
Why isn't it enabled by default?
Seems like a no-brainer optimization to do when the conditions allow it.
Also, it works in all explicit/implicit return positions, right? Not just at the end of the function?
What about mutual recursion with both functions calling each other in tail position?
There are implicit trade-off, such as losing the stack trace which could make things harder to debug.
I think you have to keep in mind though, software is about tradeoffs - just because the condition allow it does not mean the tradeoff is worth it.
One of the most popular use case is recursion, but we don't really do recursions in Ruby, so is it really worth the tradeoff then?
It's also Python's reasoning (Guido). You can read more about his blog posts here explaining his rationale, it's a pretty good read.
You can also read about the progress on Ruby's official issue tracker - bugs.ruby-lang.org/issues/6602
Yes it doesn't have to literally be on the last line, but just the last executed statement.
Not sure what you mean about mutual calling, I don't think that's really possible (I could be wrong) - Yes, you can mutual call each other at tail call position, but that does not instantly make it possible for tail call optimization; TCO is when a compiler recognizes it's calling itself (as stated in the gifs). TCO also eliminates the stackframe (can read in the issue tracker). I don't think compilers can recognize a mutual calling tail call and optimize that
ES6 spec mandated TCO for mutually recursive functions as well that both call each other in tail call position.
Only Apple browsers have it implemented at the moment, Chrome implemented it but later removed it.
One notable part of the spec was that
if (n<=0) return
would not get optimized, because there is an implicit return undefined after the last line.
I understand how that limitation came about, but I don't really see why they couldn't add a single "void" to the stack and then merrily recurse as if it's this:
if (n<=0) return
since the return value of the inner a is never used.
The tradeoffs for Ruby specifically sound like something that is more relevant in development than production, almost like something that should be a flag... Oh. That's what they did. Okay.
(I'd still suggest it be the default, until someone needs to pass --debug, but I get the idea)
Very interesting explanation. I've been using Elixir for a while now, and even though I knew that the BEAM provides tail call optimization, I did not know how it worked behind scenes, it is a goto!
Glad you liked it man! Yeah it's always interesting to see how things work behind the scene!
Just want to mention that tail call optimization is not always the best solution. Sometimes, it consumes more memory and runs slower than body recursion.
Interesting, can you elaborate on that?
P.S: And welcome to dev.to :D
After Erlang/OTP R12B, a body-recursive function generally uses the same amount of memory as a tail-recursive function and generally hard to predict whether the tail-recursive or the body-recursive version will be faster.
With around 10, 000 elements, a body-reclusive function is about 10% faster. But with larger lists (like 10, 000, 000 elements), a body-reclusive function is about 97% lower and it is about as faster as the standard library map.
Myth: Tail-Recursive Functions are Much Faster Than Recursive Functions
TAIL CALL OPTIMIZATION IN ELIXIR & ERLANG – NOT AS EFFICIENT AND IMPORTANT AS YOU PROBABLY THINK
Wow that is interesting indeed, thanks for sharing!
Why is the Elixir example considered a use of TCO? It uses a Stream for lazy evaluation but is it using TCO under the hood?
That was just a quick way for me to generate large numbers
Ah okay I get it now, thanks!
May I mark it with the triple unicorn? ) Thanks a lot!
That means a lot man, thanks! :D
Just share it to three other friend who'll unicorn it hahaha
We're a place where coders share, stay up-to-date and grow their careers.
We strive for transparency and don't collect excess data.