Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

In Scheme jargon, it has come to be that a "tail call" is in fact the name of the goto-like control transfer which replaces the regular subroutine call.

Tail calls are not eliminated; rather it is ensured that a function call in tail position is a tail call.

This is a useful view/terminology. Without the code transformation that implements tail calls, there is nothing special about a function call in tail position; it proceeds exactly like a call not in a tail position. Therefore, it is not a different kind of call. It becomes one after the code transformation: it becomes a tail call.



> ... goto-like control transfer which replaces the regular subroutine call.

That very much sounds like a [regular subroutine] call has been eliminated.

> Tail calls are not eliminated

This sounds like rather delicate semantics. A call has been eliminated. It was in the tail position. It was replaced by something else, which you call the "tail call". Maybe you'd prefer RSCITLEIOTC (regular subroutine call in tail location elimination in favour of tail call)?

I think tail call elimination is good enough to communicate the concept, whereas (when it's not purely an optimisation) tail call optimisation is not good enough, because calling it an "optimisation" is such a dangerous trap.


I don’t like that it has no special mention.

In a factorial function, fac(n-1) would be a tail call, but 0+fac(n-1) would not. It would be nice if one could annotate (and thus make the compiler verify) tail calls.


> It would be nice if one could annotate (and thus make the compiler verify) tail calls.

This always comes up, but honestly, I've never really run into the problem where I mistook a non-tail call for a tail call. (And I've programmed the majority of my career in languages like Haskell, OCaml and Erlang.)

Of course, I'm always in favour of the compiler verifying more stuff. It just doesn't seem very pressing.


It might be easier in those languages, but C (and C++), Scheme, Lisp and Nim have macros that could obscure a tail call's missing elimination.

It was never a problem for me because I never trust the compiler to do TCE and structure my code to be independent of it. But it IS a problem.


I've not yet heavily used macros in my project, but surely have used macros others wrote, sometimes perhaps even without knowing, aside from the Scheme implementations standard forms. However, I have yet to hit a case, where I thought something was a tail call, but in the end was not. It just does not seem to come up for me and has never been a problem.

Perhaps I have only used "good macros" or something, which do not make seemingly tail calls into non-tail calls?


is it possible you actually did get a non tail call where you thought it would be one, but just didn't go deep enough to blow up the stack so you weren't aware? It could be possible the call recursed "only" 10,000 times and didn't blow up any stack.

Or perhaps you've only used good macros. But it is incredibly easy for a macro to end up with (+ 0 x) where you think it will end up with "x".

It's also possible that you have used a macro that ended with (+ 0 x), the optimizer turned it into "x" and you got the tail call eliminated but were not guaranteed to get that, and it may blow up on another Scheme implementation.

That's what I don't like about it; The compiler definitely knows if it did TCE or cannot, and it does change program semantics if it did or didn't. So why can't I just annotate the code saying "well, I intend this to TCE, please complain if you can't?"


I see your point about being explicit. I think ideally the specification would be precise enough for the user to conclude: "If this is a specification conforming Scheme implementation, then this must be a tail call." I was under the impression, that this is the case with the Scheme specification, but I might be wrong, as I am not very knowledgeable in terms of the specification. If it is not so specific about what exactly becomes a tail call, then I would say, that it would be great to have an explicit way to tell the interpreter or compiler, that you want something to definitely be a tail call.


... and if you have that declaration mechanism, then it can easily work for any call in any position whatsoever!!!

So that further separates the concept "tail call" from "call in tail position".

Any position can be the tail position if we just wave he right magic wand at it, so the concept becomes meaningless. All we are left with is the style of call: returning versus goto-like.


Well, in Scheme you need to rely on TCE, if you want your code to run for more than a specific finite time. There are no other looping constructs.

Haskell does have several macro systems, but they are not nearly as pervasively used as in Lisp languages. (Mostly, because laziness makes it easier to add eg something as foreign as even eg Prolog as an embedded DSL without macros. And, of course, adding to Haskell syntax feels less natural than in Lisp.)


Exactly. The tail function in the second case is '+' not 'fac' so you can't replace the recursion with iteration


A tail call is just any call that's at the tail of a function. It might be used to refer to TCE specifically, but that's a colloquialism and would be wrong in a more technical context.


If we have no plan to treat any ordinary calls as tail calls, there is no point in identifying which are in the tail position, or using any terminology related to tail calling.

The conversion of a regular call to a tail call could easily be applied to non-tail positions (e.g. it could be requested by some "please make this a tail call" annotation). The tail position is where that can be done without changing the meaning (of programs that meet certain restrictions, at least).

The semantics of not being able to return, and not acquiring any stack-like space is what makes the call a tail call, whether or not it is in a tail position. If we make a call that doesn't return, that call is then the last action the function performs, which puts it at the tail of its activities.

The terminology also gives us nice ways to talk about vaguely related things, also, like:

"execvp is a kind of big tail call in Unix; it calls a new program, such that it's impossible to return to the original, which is entirely replaced (not just the stack frame of the caller of execvp)".

"A boot-loader branching into firmware is a kind of tail call."


Scheme 'optimizes' any tail call. Whether that's a recursive call or something else.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: