Take, for example, the following naive implementation of a right fold in Scheme:
(define (fold-rite kons knil clist)
(if (null? clist)
knil
(kons (car clist) (fold-rite kons knil (cdr clist)))))
This is obviously not a candidate for tail call elimination since the recursive invocation of fold-rite
has to complete before calling kons
. Now, I might be a little clever and use continuation-passing style instead:
(define (fold-rite-cps kons knil clist kontinuation)
(if (null? clist)
(kontinuation knil)
(fold-rite-cps kons knil (cdr clist)
(lambda (x) (kontinuation (kons (car clist) x))))))
Now fold-rite-cps
is tail recursive, but the intermediate continuations built up during recursion will still have to be kept somewhere. So while I might not blow out the stack, I'm still using about as much memory as with the first version, and somehow I get the feeling that first collecting a massive pile of continuations and then unravelling them all in one fell swoop should be bad for performance, although the second version was actually measurably faster on my machine.
But if a left fold can run in constant space (minus the value being accumulated) and a right fold on a list is the same as a left fold on its reverse, then I imagine there should be a way to implement a tail-recursive right fold that can also run in constant space. If so, how would I do that? Getting even more general, what ways are there to make a non-tail recursive function into a tail-recursive one, preferably one that can run in constant space (assuming that an explicit loop can be written in an imperative language that also runs in constant space)? Have I made any wrong assumptions?
Although I've tagged Scheme and Lisp, I'm interested in solutions in any language; the approaches should apply to functional programs in general.