Compiler doesn't optimize a function. It optimizes a call to that function. It may be the call to the same function inside it, it may be a call to different function, it doesn't matter.
...which isn't the case with Clojure, where recur
can only return execution to the latest "recursion point" (be it loop
or fn
) which makes mutual recursion impossible without "help from the outside", such as a trampoline. It's more like a bandaid than a solution, but the problem is too big and a proper solution would require too much.
Yes, there is a way to make sure it gets optimized in Elixir: it needs to be an actual tail call. That's all.
In computer science, a tail call is a subroutine call performed as the final action of a procedure.
This can easily be determined by visually inspecting the code.
If Clojure were to work this way, the following two definitions would be equivalent:
(defn x [] (x)) ; <- StackOverflowError if called
(defn x [] (recur)) ; <- hangs if called
But if you want to enforce this and fail otherwise, you have to do it either:
- before compilation, e. g. with static code analyis using some markings for "desired TCO"
- before deployment, e. g. with tests that would trigger stack overflow
The latter seems more practical, easiest with pure functions.
I haven't found any existing solutions that do that.