Last call generalised

Marcin 'Qrczak' Kowalczyk qrczak@knm.org.pl
Sat, 30 Aug 2003 01:46:15 +0200


Dnia czw 28. sierpnia 2003 23:04, b.i.mills@massey.ac.nz napisał:

> > copyList (x:xs) = x : copyList xs
> >
> > is surely not tail-recursive in the traditional sense, but I think
> > that most Haskell programmers  take it for granted that it runs in
> > constant stack space.

The problem lies in the fact that the execution of a Haskell "function" can 
be interleaved with the execution of the code which calls it. Counting 
total stack space ever consumed by parts consisting of a function is 
meaningless because between these parts the stack is usually unwound.

If you implemented this in a strict language, you would probably attribute 
only building of the cons cell to the invocation of the function. The cell 
points to a thunk in its tail, so when the tail is evaluated, an anonymous 
function is called - the original function is long finished.

> I brought up the same issue some time back about >>. That is
> in func = f x >> func, we have the problem that >> is a function
> so func is not a last call.

It's an easy problem: although func is not a tail call, >> is a tail call 
and >> itself in most monads enters its second argument in a tail 
position. I would name it an indirect tail call.

> In procedural programming, the idea of the last call is that
> there is an operator ; that is often thought of as separating
> statements.

In Scheme not only sequencing generates tail calls; e.g. branches of 'if' 
are in a tail position wrt. the 'if' itself, the body of 'let' wrt. the 
whole 'let' etc.

If these constructs were implemented as plain functions taking closures as 
parameters, you would derive whether they tail call some of their 
parameters from their implementation. A Scheme definition, which describes 
the semantics and not a concrete implementation, would probably specify 
which functions on which conditions are required to tail call which of 
their parameters. But Scheme prefers macros, so I think the language 
definition doesn't talk about tail call properties of standard functions - 
because there aren't any interesting functions to talk about.

OTOH Haskell uses more functions instead of built-in syntactic constructs, 
because passing a parameterless closure is very easy - you just write the 
expression consisting of the body. This makes meaningful to ask which 
functions on which conditions enter some of their parameters in a tail 
position. For example && does this with its second parameter if it's 
entered at all.

-- 
   __("<         Marcin Kowalczyk
   \__/       qrczak@knm.org.pl
    ^^     http://qrnik.knm.org.pl/~qrczak/