Thinking About Recursion
solipsys.co.uk
solipsys.co.uk
So recursion is just realizing that I can do the complex bit by calling the current function!
Of course, this sometimes means that the function signature needs to change, to (say) include a collecting list or something. But that's the general idea.
I'm not sure if this has ever helped anyone :-)
https://youtu.be/amf5lTZ0UTc?t=3501
"...but since I know [the function X], by wishful thinking, will produce the simplified result, then I don't have to think about it anymore. I've used it. I've used it in a reasonable way. I will get a reasonable answer."
I didn't understand recursion too well until finishing chapter 2 of SICP.
https://htdp.org/2020-8-1/Book/part_two.html
By far the greatest educational piece I've come across on this subject matter.
>"It might be worth noting here that some (most?) early programming languages (specifically Fortran 77, for example) didn't allow a function to call itself - recursive calls were explicitly forbidden."
Does anybody know why this was?
In general, recursive calls require stack frames, which is main memory. Tail recursion is however relatively easily transformed into the equivalent while loop, which then does not consume stack frames.
Consider:
def factorial(n):
if n is 1: return 1
else: return n * factorial(n - 1)
For factorial(12345), this requires 12345 stack frames, but with tail-call optimization the compiler can transform the function into something this: def factorial(n):
accum = n
while n > 1:
n -= 1
accum *= n
return accum
Besides this, some older languages just didn't have a real stack. All variables would be allocated as-if they are global variables and there would be a small array of return addresses and the current index into it somewhere. Naturally, you could not nest function calls deeper than this array was long.For Fortran the reason may be that Fortran was (and still is) used a lot for numeric code and loops lend themselves more naturally to optimization and vectorization compared to arbitrary recursion.
All can be solved with iteration. E.g. for Hanoi:
max = 1 << no_of_discs;
for (x = 1; x < max; x++)
printf("move a disc from %d to %d\n", (x&x-1)%3, ((x|x-1)+1)%3);
In the case of fibonacci, the iterative solution is exponentially faster as well.Perhaps quicksort is a better example that cannot be easily expressed with iteration.
(edit: grammar)
The Ackermann function (or rather, the family of functions with similar behavior globally known as 'ackermann') is an example of a general recursive (i.e. computable) function that is not primitive recursive. The proof that this is the case is a classical result that's part of the usual computer science curriculum, the idea being that the asymptotic growth of Ackermann is greater than the asymptotic growth of any primitive recursive function.
However, it's easy to see that you can write any recursive function iteratively; it's sufficient to allocate memory for a stack-like structure, and wrap the function body in a while loop. Recursive calls are pushes onto the stack, and returns are pops from the stack. This is exactly how compilers are able to turn recursive calls into ASM. Notice that there is no guarantee that, in general, the function will ever exit the while loop. This is inherent; it is in fact the recursive variant of the halting problem.
Generally, it is easy to turn a recursive algorithm into a loop and a stack. Simulating a CPU and running compiled code is one example of running recursive programs while only using a loop.
I wrote a version of the Ackermann function in Brainfuck (a language with only while loops), but sadly I don't know where I left the program.
I'm going to write a follow-up post to this with a genuine real-world (well, pure maths calculation) example ... perhaps you will find that more satisfying.
Tree- and graph traversal is often easier to reason about using recursion from my experience.
Worst case, you can get the same effect by pushing recursive calls on a stack/queue that's consumed iteratively.
I don’t know, maybe this is just a new programmer meme. The number of programmers have supposedly doubled every five years, so I imagine internet programming discussion would be dominated by new programmers, and would have been dominated by new programmers for the last several decades.
I think part of it was that to use recursion properly in this case, you'd have to return multiple values, and that complicated things, particularly if they were using a language like Java where it's not built-in.
In imperative languages, parsers are often written using the fields of an object or struct to hold the parser state. But if you're just writing a single function then it becomes more natural to keep the parser state in local variables, and recursion means you don't have easy access to it.
I couldn't agree more. One down side of recursion can be that if you need to refactor you might find recursion is no longer the better solution. Unwinding that can be a pain.
1. Learn
2. Review
3. Teach
The topic on recursive data structures in linked pdf is even more fun!!!
[1] https://www.cs.cmu.edu/~15110-f12/Touretzky-Common-Lisp-ch8....
In order to understand recursion, you must first understand recursion.