Tail calls in Valeri

With the latest additions to Valeri, you can now do tail calls. Tail calls are a way to fully "replace" the current function call with a new function. This is useful if you aren't going to alter the result of a nested call in any way, and instead just return it back to the parent.

Most often than not, programming languages with tail calls perform them implicitly. This means that if a compiler can "prove" that a function call you're making is the last thing current function will ever do, then it's safe to just disregard the current stack frame. This is cool because it doesn't require you to think about it and mostly just works.

Some mainstream languages (like Python) decided to not introduce tail calls, because they make debugging harder. A user may be surprised if instead of a stack of nested calls, they only see one in the debugger, with no ability to see the value of variables in the intermediate frames.

In Valeri, I'm making a balanced choice to allow tail calls with a special tailcall form. You pass a function and its parameters to this form and it will perform the tail call as you would expect. Here's an example:


(fn countdown (n)
    (when (> n 0)
      (println n)
      (tailcall countdown (- n 1))
      )
    )

(count 100000)

In this example, we are "recursively" calling the countdown function. Without a tail call, it will result in a stack overflow error. But with a tail call, the frames that are no longer accessible are just garbage collected, and the call stack always has a depth of 1.

The reason why tail calls are necessary in Valeri is because this allows to do iteration over an integer range (like in the example), or any other types of iteration that would otherwise require a mutable variable. By using the tailcall form, you can implement map, fold, zip, and other useful higher-order functions.