Path: utzoo!attcan!uunet!lll-winken!lll-tis!ames!mailrus!uflorida!novavax!proxftl!bill From: bill@proxftl.UUCP (T. William Wells) Newsgroups: comp.misc Subject: Re: Basics of Program Design Message-ID: <464@proxftl.UUCP> Date: 13 Jul 88 06:59:06 GMT References: <901@td2cad.intel.com> <3061@rpp386.UUCP> <395@proxftl.UUCP> <53374@ti-csl.CSNET> Distribution: na Organization: Proximity Technology, Ft. Lauderdale Lines: 212 Sigh.... Some people just can't seem to read. My first posting in this series, <395@proxftl.UUCP>: 1) Queried the definition of tail recursion. 2) Lambasted a poster for saying an algorithm was good because: "any good compiler [would get rid of the recursion]", in spite of the fact that essentially all C compilers do not. 3) Asserted that the choice of algorithms is determined by pragmatic factors, not just the theoretical characteristics of the algorithm. In my next posting, <430@proxftl.UUCP>, I: 1) Said that I've been in this business a long time and have worked with a variety of systems. 2) Pointed out that the only compiler that anyone says can do tail recursion, gcc, applies to a single processor, and that my work requires porting to over a hundred compilers. 3) Asserted that because of this I can not rely on compilers to do tail recursion. 4) Pointed out that tail recursion is equivalent to a while loop and that is the preferred method of coding this kind of algorithm. [The implied context is C, I'd not say this for some other languages.] 5) Asserted that recursion often needs to be checked carefully because it consumes resources. 6) Gave examples of how I dealt with some recursion: one by leaving it, it being the better code, and another by fixing it, as its unbounded recursion would cause problems. 7) Responded to a gratuitous insult with one of my own. 8) Pointed out that I have a lot of experience in optimizing C programs. 9) Proposed that the argument that a routine is going to be used a small fraction of the time is no excuse for sloppy coding. 10) Gave an example of where that approach could cost one, and another example where a sloppy implementation of sscanf accounted for 35% of the execution time of a program. 11) Pointed out the way to become the best programmer you can be is to try to program as well as possible, all the time. 12) Pointed out that a recursive strlen is just plain stupid. Nowhere did I suggest that there is anything wrong with recursion per se. So let me say this real clear for those of you insist on interpolating your own fears and fanaticisms into other's writings. Recursion is a programming tool. Like any tool, it has its uses and limitations. Under certain circumstances, coding it any way but recursive is absolute stupidity. Under others, using recursion should get you condemned to writing RPG programs for the rest of your life. In many other circumstances, a strong case can be made for using recursion, or for doing it some other way. And in other cases, the use of recursion is purely a matter of taste. With that taken care of, I'll address the points raised in the article I am responding to: In article <53374@ti-csl.CSNET>, gateley@mips.csc.ti.com (John Gateley) writes: > In article <430@proxftl.UUCP> bill@proxftl.UUCP (T. William Wells) writes: > >[...] > >our code is regularly compiled with. Relying on compilers to do > >tail recursion optimization would be slitting our own throats. > >[...] > >Sorry, but using purely tail recursive routines does not save > >time for this programmer. Anywhere you would use tail recursion, > >I'd automatically code a while loop. (See below for why.) This > >[...] > >Also, another real-world point: recursive routines in general > >need to be used carefully, to avoid excess resource consumption. > >As an example, I recently translated a rather complicated parsing > >routine from SCHEME to C. In the translated version were several > >recursive routines. One of the recursive routines was left alone, > >the greatest depth of recursion was the length of a grammar rule > >and removing the recursion would have been a real pain. Another > >got fixed (even though it required extensive recoding) because it > >was not possible to bound the stack usage. Our customers WILL > >NOT let us chew resources with wild abandon. > >[...] > >efficient coding becomes second nature. Then you will naturally > >write efficient programs and as a matter of course will not do > >stupid things like writing strlen() recursively. > > The tail-recursion debate has been going on for a while, and I would > like to defend tail recursion, recursion, and optimizations in general > against the points made above. There is flatly no defense against those points. Fortunately, the rest of your article does not really try to defend against them, so you actually say something useful. 1) It is a fact that our code is ported to compilers that do not handle recursion well. This is not avoidable. (Do YOU want to write me a good compiler for an 8051 or a 7809?) We can not avoid this fact; therefore, we must deal with it. That means that often we may not write routines that do extensive recursion. And for others, should you want to write portable code, you ought not to assume that you have megabytes of stack space. You won't. 2) Tail recursion, as a programming technique, is equivalent to iteration. Given that most compilers do not properly handle tail recursion, that makes the iteration the better coding technique. Unless, of course, one is a gcc chauvinist, in which case, one's opinions are irrelevant. 3) Like any other programming technique, recursion has its costs; failure to properly consider them, in the context of the task at hand, is bad programming. Period. 4) Strlen has absolutely no business being coded recursively. And while a recursive strlen algorithm might be an interesting exercise in alternative thinking, it is simply the wrong way to do it. It is just as silly as those example factorial algorithms which I suspect mislead the individual who wrote the strlen. An algorithm implements an idea; the idea of strlen is that of the number of characters in a string. The recursive implementation of it simply fails to capture this property, except, perhaps, to a mathematician, or someone else who counts using Peano's axioms. > First: tail recursion has been only > discussed as a "true" recursive call optimization in these postings. > It also applies to many non-recursive and indirectly recursive calls. > Tail recursion optimizations serve several more purposes than what has > been brought out here: it does not require pushing a return address on > the stack (no stack growth, this is the optimization that has been discussed > here); it allows more efficient argument passing (in the self-recursive > case, all you have to do is change the arguments on the stack, not push > new ones); it eliminates the need for saving register variables on the > stack; and probably several more that I dont know about. I prefer these > optimizations to take place. These are good points in favor of adding these optimizations to a compiler; they have to be balanced against the fact that there is an almost unbreakable vicious circle that will make it unlikely that these optimizations will become general. > It has been mentioned that it is hard to > debug with a tail-recursion optimizing compiler. I disagree and I use > one every day. I too thought that objection was silly. > As for recursion: it is a tool just like while loops. It is a useful > abstraction: many algorithms are much more simple in a recursive than > non recursive form (my opinion of course). I feel that in order to get > the most out of a language, it should have good tools (including recursion). > It is up to the compiler writers to make those tool perform well (including > being efficient). For example, there is a simple transformation which > completely removes the stack from a recursive program (translating the > program into continuation passing style). This is useful for compiler > writers: they can use it where appropriate to make the program execute > more efficiently. I agree with this; however, remember that we are writing programms today, we can't write inefficient programs, giving the excuse that someday a compiler writer will make them efficient for us. (Well, ok, we can; would you hire someone who thought like that?) [B.T.W., doesn't a continuation have a context, and isn't that context equivalent to an activation record?] > I have a story which is similar to some that have been posted. In college, > one of my homework assigments was to determine if a graph was connected. > The graph was represented as a array with 1's for edges. I thought that > recursion was terribly inefficient, so I just multiplied the matrix against > itself the neccesary number of times. I also did it recursively. Guess which > one was faster! The recursive one of course. A good illustration of the tool-ness of techniques. Another example of this is a parsing algorithm that is O(n**2.78) (I think, anyway < 3). It too relies on matrix multiplication. Standard parsing methods are O(n**3) or worse and tend to be recursive; however, the constant factor for the "faster" algorithm is huge. On the other hand...would that change for a parallel system? Or one especially designed for matrix multiplication? > I do not feel that recursion and/or tail-recursion are bad things. They > are extremely powerful tools, and like all tools (especially extremely > powerful ones) they must be used with care. But, if you think about it, > just about any programming language tool must be used with care. No disagreement here. Though I'd emphasize that the care required with recursion is of a slightly different kind than for most other control structures. When I write a while loop, for example, the care is that I get the algorithm right; the cost of a while loop is usually evident. When I code recursively, not only must I get the algorithm right, but I must also consider the space and time consumed by the recursive calls. This requires a significantly more complex analysis than a while usually requires. The power of recursion is a good reason for emphasizing it in programming courses. As long as they also teach techniques for estimating the cost of the tool. And grind it into the student's head that these cost estimation techniques are necessary.