This Function is Not Tail Recursive
codemonkeyism.com
codemonkeyism.com
The ability to parallelize purely functional code comes from the Church-Rosser theorem (http://en.wikipedia.org/wiki/Church%E2%80%93Rosser_theorem), which states that you can do beta reductions in any order in the lambda calculus and get the same term.
The thing with tail-recursive code is precisely that there is only one way to do a reduction - the function has to call itself at the end. That's what makes tail-recursive code "iterative."
As soon as you introduce multiple reduction paths, you need a stack for the intermediate results - think of for example a recursive divide-and-conquer algorithm.
This Kornelius Fuhrer dude shouldn't be writing articles on parallel programming, he should be reading books about it.
Guy Steele gave a very good talk about the problem (basically, "tail recursion considered harmful") and solution ("divide and conquer") at ICFP last year: http://docs.google.com/viewer?a=v&q=cache:bBDVKW8DP6UJ:l...
APL-style operators (map/reduce, paralations, NESL, etc.) are definitely the way to go. Not only do you get parallelization "for free," but optimizations like map fusion (see http://www.randomhacks.net/articles/2007/02/10/map-fusion-an..., and Rich Waters' earlier work on SERIES: http://series.sourceforge.net/) become easy - something like that is almost impossible to do with loop analysis. All that, and you end up with clearer, shorter code. Due to all these reasons, I'm convinced that automatic loop parallelization is the biggest waste of time in parallel computing research.
fac1 0 = 1
fac1 n = (*) n (fac1 ((-) n 1))
-- tail-recursive
fac2 0 acc = acc
fac2 n acc = fac2 ((-)n 1) ((*) acc n)
To me, something in with a pattern of:f v = g v (f (t v))
where f, g and t are functions (t is a transform on a, in the factorial case it is (-),
Can usually be turned into the form (mechanically even):
f v acc = f (t v) (g v acc)
Does this hold for all cases or am i missing something? If it does hold, could a language just implement the TCO for it, and therefore couldn't we just consider the very first implementation of fac1 to be tail-recursive?
edit: It is ugly but changed my exmple code to match the pattern i describe exactly.
For example:
blah 0 = []
blah n = (++) [n] (blah ((-) n 1))
-- blah 5 -> [5,4,3,2,1]
blah2 0 acc = acc
blah2 n acc = blah2 ((-) n 1) ((++) [n] acc)
-- blah2 5 [] -> [1,2,3,4,5]
If we changed the tail to: blah2 (n-1) ((++) acc [n]) it would work in this case, but then we've flipped the arguments, and I have a feeling we could come up with a function where simply flipping the arguments won't work, though I can't think of one at the moment.
blah 0 = []
blah n = (:) n (blah ((-) n 1))
-- blah 5 -> [5,4,3,2,1]
blah2 0 acc = acc
blah2 n acc = blah2 ((-) n 1) ((:) n acc)
-- blah2 5 [] -> [1,2,3,4,5]
The benefit, as "everybody" here of course already knows, is to reduce memory (call stack) usage.
Knowing how to implement tail recursion allows you to write in an "assign only once" type of language, without blowing up the stack. And using such "invariant symbols" in a routine allows the compiler/interpreter to seek parallelization at other points in the function on your behalf. OK, so I just contradicted myself, but the point is there is an rather indirect connection between tail recursion and parallelization.
return n == 0 ? 1 : n * fac(n -1);
becomes
return n == 0 ? 1 : multiply(n, fac(n -1);
This makes it a fair bit clearer, IMO.
Imagine if it were 'treturn' or something.
It's not something that bites people who have been around this mulberry bush before, but I think it can very likely catch problems that novices have, thinking they're writing an O(1) space solution but actually using O(n) stack.
Deleted comment
Also, let us not forget that tail recursion is a specific form of the more general tail call optimization. For example, the following is two TCO-able functions calling each other:
functionA(...):
// do stuff
return functionB(...)
functionB():
// dostuff
return functionA(...)
Whether or not the compiler optimizes this, however, is a completely different question.If somebody could provide an example of a tail-recursive function that would be much more difficult to code as a loop, I'll eat my words.
recur is still functional, it resembles a loop but isn't one so nothing is mutated, and you can even nest them. And you can recur over a loop or the function itself. Handy.
For those who aren't familiar with tail-call optimization, here's a very simplified explanation: if the very last thing you do in a function is call it recursively, then instead of allocating the space for its local variables (and any other local state it might have) for another call, you can simply set the arguments to their new values, reset the local state and do an equivalent of "goto" to the beginning of the function.
I guess Java's infix notation kind of obscures this. This is one of the reasons why functional programmers prefer to consider everything a function. It's also why some languages (like Clojure) have special syntax for tail calls, with special error messages if you goof up the semantics.
In Haskell, which has more syntax, you can use * in infix notation, but so you can with other function. E.g. the function `on`. Quoting from the documentation:
on :: (b -> b -> c) -> (a -> b) -> a -> a -> c
(*) `on` f = \x y -> f x * f y.
Typical usage: Data.List.sortBy (compare `on` fst).n * fac(n-1) vs. * n fac(- n 1)
or * (n, fac(n-1)) as a method call, which makes it clear * is the last function that's called.
A function is tail recursive when the last thing it does is call some function, possibly different. Yes, you can have a mess of different functions that are mutually tail recursive. For an example of how this comes up, think "state machines" with each call being another state transition.
Not a tail call:
var even = function (x) { return x == 0 || !even(x-1); }
Mutually Tail Recursive: var even = function (x) { return x == 0 || odd(x-1); }
var odd = function (x) { return x == 1 || even(x-1); }"So please, do not call all functions where the last function call in your source code is a call to itself, tail recursive. A function is tail recursive, if the last operation is a function call to itself."
It reads to me as "Don't call functions that call themselves last tail recursive. A function is tail recursive if it calls itself last."
That's saying the same thing, if my reading of it is right. His oddly-placed commas don't help.
function foo(n) {
...
return n+foo(n-1);
}
is not tail recursive because the last operation is not the recursive function call, but rather the addition. function bar(n,acc) {
...
return bar(n-1,acc+n);
}
is tail recursive because the last operation is now the recursive function call.This means that a compiler can optimize away the saving of a new stack frame for a recursive call, because the old stack frame of the enclosing call will never be used again. That's tail-call optimization.
If there's stuff in between the return from the recursive call and the return from the enclosing call (like the multiply in this factorial example) then the old stack frame will have to be kept around which means you must use unoptimized recursive calls.
In other words, it's the difference between O(n) and O(1) memory usage.
Well, this purpose was to make it a tail call so that it could be optimized. Since it either is or it isn't, "close enough" to "is" is, sadly, "isn't". Which turns it from O(1) to O(n).
In computer science (and generally, in mathematics), every word of the dictionary definition matter.