my revision to the intro was because i thought the actual statement of what tail recursion really is had gotten a bit buried. Bgruber 04:52, 6 May 2006 (UTC)Rep
| This redirect does not require a rating on Wikipedia's content assessment scale. It is of interest to the following WikiProjects: | ||||||||||||||||||
| ||||||||||||||||||
my revision to the intro was because i thought the actual statement of what tail recursion really is had gotten a bit buried. Bgruber 04:52, 6 May 2006 (UTC)
I suggest the following revision to the Introduction. However, I do not wish to make such a change without submitting it to this review process. The reason I feel such a change is necessary is that it is not evident from the previous version of the intro that tail recursion adds efficiency; it seems to be demonstrating iteration to be superior to it, when in fact it should (in keeping with later sections of the article) be emphasizing that tail-recursion can be more efficient than other forms of recursion because it can be automatically converted to iteration.
"In computer Science, tail recursion (or tail-end recursion) is a special case of recursion in which the last operation of the function, the tail call, is a recursive call. Funcitions using tail recursion can easily be converted to use iteration instead, either manually or automatically. Doing so is beneficial because it can drastically reduce the amount of stack space used and improve efficiency. Tail recursion is often used in functional programming languages, in which the declarative approach and explicit handling of state promote the use of tail-recursive functions in place of other recursive functions that would otherwise rapidly fill the call stack."
If someone at a later time feels that this intro is appropriate, and has gone through sufficient review, please don't wait for me to change the article: go ahead and do it. --Fromageestciel (talk) 16:48, 28 September 2009 (UTC)
Isn't the second point in the recursive definition backwards? Shouldn't
be rather
? --Mormegil 14:29, 13 Oct 2004 (UTC)
Previous tail recursion edit and Tail recursion differ only by <pre> being replaces with <code> tags, yet in firefox 1.0 <pre> yields 6 boxes and <code> yields 11 boxes! -- Dbroadwell 20:10, 17 Feb 2005 (UTC)
Does this sound strange to anybody else? EmilioSilva 00:02, 15 September 2005 (UTC)
The section about tr modulo cons currently has examples in C, which I'm afraid not everyone may be able to read; esp. an expression like malloc(sizeof(list)) may be very confusing. I've rewritten the first example in pseudocode (below), but I'm not sure how to express &(*p); are there conventions for working with pointers in pseudocode?
function f(input : List) returns List
if input = nil
return nil
else
var n = new Link
n.value = 1
n.next = f(input.next)
return n
Qwertyus 13:08, 15 April 2006 (UTC)
Or what about a language that actually uses cons?
(define (f input)
(if (null? input)
()
(cons 1
(f (cdr input)))))
72.93.87.3 (talk) 13:47, 10 September 2009 (UTC)
On newer microprocessor architectures hardware the branch predictor cannot predict function calls which isn't paired with returns. The performance hit can usually be triggered up in the call stack after the code has finished executing and returned, in completely unrelated code which does not mess with the stack.
I'm confused by:
In some cases (such as filtering lists) and in some languages, full tail recursion may require a function that was previously purely functional to be written such that it mutates references stored in other variables.
I think this is trying to say either for efficiency reasons, some functions are re-written to use mutation, or if you have shared data, then operations like filter may want to mutate their inputs? I'm unsure. Either way, this is nothing specific to tail recursion. --Ian Barland Not-just-yeti 14:31, 14 December 2006 (UTC)not-just-yeti
I just checked, and gcc, g++ and Java all fail to optimize a one-line tail-recursive function. Why? It might be nice have a paragraph trying to justify this. The only explanation I currently see is "making a complete call graph is a daunting task for a compiler". And in general, mutual recursion (esp. across files) might be problematic. But why not just check for a function recurring on itself, and optimize that? (The reason I'm looking at this wikipedia page is because theEuclidean Algorithm page made a claim that tail-recursion is inherently inefficient, whereas I thought it was more of lazy compiler-writers :-) --Ian Barland Not-just-yeti 14:31, 14 December 2006 (UTC)not-just-yeti
The article states 'When a function is called, the computer must "remember" the place it was called from, the return address, so that it can return to that location with the result once the call is complete'.
Not necessarily - this is implementation dependent. Henry Baker somewhere discusses treating calls as a sort of dynamic macro, expanding each function token and pushing that onto an execution stack after popping the original occurrence of the function token. There is no explicit return at all, with the execution stack unravelling as it goes (until, presumably, it reaches a sentinel at the end). Even though there is apparently more overhead, Henry Baker suggested that if a language typically has small functions, with modern high speed caches this might be competitive. Also the overhead would obviously be immaterial for some user I/O bound scripting languages. P.M.Lawrence.
You are talking about machine with an instuction and a data stack.
It simply starts executing on the data stack using the instruction at the top of the instruction stack. Instead of function calls it simply copies the instructions that define the called function to the top of the instruction stack and proceeds by executing them. It follows that there is no need to explicitly do a return instruction, since when all instructions of the called function are executed, automatically the next instruction on the stack is the one after the call. Still even if the return address is implicit like it is in this scenario, I don't think the current text:
When a function is called, the computer must "remember" the place it was called from, the return address, so that it can return to that location with the result once the call is complete.
is false, even if the formulation is perhaps a bit biased towards register machines. Your own:
In a typical implementation, when a function is called the computer must "remember" the place it was called from, the return address, so that it can return to that location with the result once the call is complete (this does not happen when calls are implemented by dynamic macro expansion to an execution stack).
is false instead, claiming the return address is not remembered.
If the continuation of a call is not stored in some way, then it is impossible to proceed with its execution after the call. It must be stored explicitly as a return address or implicitly in an instruction stack or some other way. --MarSch 18:02, 12 July 2007 (UTC)
Now you are wriggling. With this implementation there is no remembering involved apart from that provided by a program counter (or instruction pointer, or whatever you wish to call it). As at the point where the "call" - dynamic macro expansion - occurs, that PC or IP is changed. There isn't even your wriggling "implicit" return address, as it cannot be distinguished from any other address later in the sequence. There is no requirement whatsoever to remember the value the PC had immediately previously, and no way to reconstruct it.
I suggest you reinstate the information I provided, or reword it. If you do not do so, you are wilfully adopting the view that if you don't like an edit, or don't understand it, even if there are authorities in support of it, you will remove it. That is pretty much what vandalism is, so if you do not do something constructive within a period of grace of three days I shall reinstate pretty much what I put before. If you persist in removing what I have now demonstrated is factually correct, I shall report it as vandalism.
N.B., further argument on the point is not constructive. Making suggestions for alternative phrasing is. I notice that you did not respond to my request that you explain "why you undid it, as opposed to clarifying it further yourself" (emphasis added). I have been inviting you to be constructive and you have signally ignored the option. P.M.Lawrence.
Did you know that you can type four tildes in a row — ~~~~ — to sign and date your posts? It's considered good form to sign and date comments on talk pages, so other readers can follow the chronology.
I've read Baker's paper now; it turns out that you quoted everything Baker had to say on the subject, so I wasn't able to satisfy my curiosity about the tricky bits of the idea. (I Googled up a post on the TUNES mailing list that gets about as detailed as Baker.) I was hoping to get back here before you called me on the "local branches" thing, though; I see now that in the "instruction stack" paradigm, one wouldn't have the concept of a "branch", but merely a lot of very small functions, mostly tail-recursive. However, I don't see what the idea buys you. You have to implement a hardware stack, which AFAICT is impossible to "get right"; either you make your expensive fast-memory "top of stack" buffer too big, in which case you waste resources, or you make it too small, in which case you lose efficiency. (Or you do something totally different and cool that we all missed.) Niklaus Wirth has a very interesting essay called "Good Ideas: Through the Looking Glass"[2] in which he says the same thing about register windows, which strike me as very similar to the "instruction stack" idea: in both cases we're taking something with a pretty decent "traditional" implementation (normal register set; normal instruction cache) and deciding that it would be more theoretically attractive if it were implemented using a hardware stack instead.
Anyway, as soon as your instruction stack gets too big, I think you have to start paging out the lower part into RAM. Which means you're constructing — albeit inefficiently — a stack growing somewhere in RAM, containing the next instructions to be executed when the current procedure returns. (You can make it more efficient by only storing those instructions' addresses in the stack, of course.) And then what do you have? A call stack!
If you can provide any evidence that this "instruction stack" idea actually works in hardware, in some way that is not dependent-on or in-the-worst-case-isomorphic-to the traditional "call stack" paradigm, then (1) it'll be worth including in the article, and (2) I'll be very intrigued. Until then, though, the claim that an "instruction stack" magically makes the call stack go away strikes me as an extraordinary claim requiring extraordinary evidence to support it. --Quuxplusone 06:49, 14 July 2007 (UTC)
I just thought I'd chime in to say that most of this discussion and indeed much of the article as it currently stands seems to focus on programming language design, compiler design and computer architecture, as opposed to the "pure computer science" concept of tail recursion. Surely these are important related topics, and there is ample room in the article to discuss how tail recursion impacts upon them, but I think the article would be best served by putting those parts in separate sections and labeling them appropriately. As for this particular debate, I think such a labeling would help to solve the problem: P.M. Lawrence is correct when he says that the "description" section is imprecise in that such statements about return addresses are implementation dependent, and others are correct in that a major reason why tail recursion is an important concept is that what i believe to be a vast majority of architectures utilize such an implementation. Perhaps to start with the "description" section should be renamed "tail call optimization." Looking around, I would say that the article Recursion (computer science) actually contains a better description of tail recursion than this article. Bgruber 01:05, 16 July 2007 (UTC)
IMHO The main point of distinction for tail recursion (as opposed to general recursion) is that it can be optimized. I know that Common Lisp and SCHEME optimize tail recursion intelligently. The wording of the intro suggests even tail recursion is always inefficient. I assume that is not intended. See Paradigms of Artificial Intelligence Programming by Peter Norvig page 63.
Also IMHO, the functional equivalence and mutual transformability of recursion and iteration is true and important. But it is confusing to mention it in an intro to tail recursion. Can this be put somewhere else?
BTW: AI employers often ask applicants if they know about the significance to efficiency of tail recursion. ClickStudent 14:35, 14 September 2007 (UTC)
This article starts by talking about tail recursion and then gets lost in talking about tail call optimization.
A tail call is the general case where a procedure calls a procedure as its last action and does not need to do further processing of the result. A tail recursion is a special case of a tail call where the procedure calls itself. Tail recursion is much easier to optimize than general tail calls. All it requires is that your target instruction set/language have an equivalent of jmp or goto.
In particular, that means that for the most common compiler targets - C, virtually any machine code, and several virtual machines (JVM and CLR) - tail recursion can be done with little effort. The compiler can just emit an imperative loop with a jmp/goto from the bottom of the procedure to the top (note, on the JVM Java does not do this optimization but Scala does).
The more general tail call case is what may require all the more advanced stuff this article talks about such as trampolining, garbage collected stack frames, etc.
Second, the article says that Scheme requires tail recursion optimization. That's true, but not the real story. Scheme (along with Haskell) requires full blown tail call optimization. Of course, that implies tail recursion optimization as well because a tail recursive call is just a special case of a tail call. —Preceding unsigned comment added by 149.63.92.58 (talk) 16:47, 17 October 2007 (UTC)
Some reasons why I attempted the minor changes to the opening:
A bigger problem with the opening paragraph is that recursion is about algorithms, whereas tail-recursion-optimization is about implementation (as the previous section alludes to).
The second Scheme example should look similar to the first except for the change to accumulator style. There is no need to use a lambda instead of the function definition short hand. Also, why is there this addition of an assertion, especially if the contract states number -> number.
May I suggest:
(define (factorial n acc)
(if (= n 0)
acc
(factorial (- n 1) (* n acc))))
If it is really necessary we could do
(define (factorial n)
(factorial/acc n 1))
(define (factorial/acc n acc)
(if (= n acc)
acc
(factorial (- n 1) (* n acc))))
129.10.116.251 (talk) 12:43, 10 June 2009 (UTC)
not-just-yeti (talk) 14:44, 15 May 2008 (UTC)
The two articles are about the same subject, but the other is vastly inferior. However, the correct name is the one of the other article. In most cases general tail call optimization is as easy as tail recursion optimization (see the discussion in the freely available book "Programming Languages: Application and Interpretation").--Blaisorblade (talk) 01:24, 23 July 2010 (UTC)
Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.