How not to teach recursion (2021)
parentheticallyspeaking.org
parentheticallyspeaking.org
The author argues that nobody uses or needs neither factorial nor Fibonacci numbers.
But that is not the point. If you are going to teach a concept it's best to use easy to understand examples. And factorial Fibonacci numbers are just that. I rather use factorial to teach recursion than QuickSort using Hoare partition algorithm.
And if it comes about teaching I think it is very important to so teach tail call recursion and to teach when to use recursion. Because it can be a waste to use recursion if an easy to implement iterative solution exists.
>>> In principle, natural numbers are also recursive data: a number is either zero or the successor of another natural number. However, this way of thinking is not natural to students...
That's because students didn't learn math. "Back in my day," we learned mathematical induction sometime in middle school, and were re-introduced to it from time to time as a style of proof. When I finally got a chance to learn programming (in 1981), recursion was taught as something we already knew, being related to induction.
The factorial example is trivial and too familiar. It's easy for students to think "Oh, I know that" when in fact they don't get it at all.
Recursive operations on data structures are more likely to be unfamiliar. And you need something unfamiliar to demonstrate the point and also illustrate how it's likely to be used in practice.
Unless you are assuming ZFC, which is much more comlplex then recursion itself, in second-order logic Induction is an axiom that we use to avoid recursion, by setting up the recursive step and then saying "so we don't have to execute it and look further"
Constructing the naturals is dynamic programming (building up), not recursion (breaking down).
Calculatiny addition using Peano axioms, that is recursion.
So a student might be familiar with it.
I think recursion should be taught with examples that would be easier for a student to write with recursion than without it. For example, listing all files in a directory.
Walking a tree / BFS is quite easy to implement iteratively, using a search queue. It's a common example for teaching Lisp.
Recursion is most natural in problems like implementing evaluation of an abstract syntax trer:
eval(tree) =
apply(node(tree),
map(eval, leaves(tree))
)O(n^2) but very simple.
Tail call recursion is interesting, but that belongs in an FP course, which is usually taught long after the basics of programming.
Everyone knows that you don't _really_ want to try doing 123! using your "my first recursion" code in C. Similarly everyone knows that factorial gets pretty big pretty quickly – I remember finding out the limits of calculators in school by finding at which point x! went from "big number" to "error". It's pedagogically useful for those things and you can see (a) if your answer is right, compared to a BigNum library, and (b) how long it takes. There's a whole can of worms you can go down, from Sterling's approximation to a discussion about time complexity, ints and IEEE 754, and stack overflows. You could then go waffling about, e.g. Haskell and lazy evaluation and functional programming and so on.
Factorial isn't a good problem but it's common enough that students will have heard of it, deep enough to be interesting, and used so frequently as a programming example that if you see a recursive definition of factorial in another programming language you get that they're trying to teach you about recursion. It's a good teaching tool for all those reasons.
the biggest lesson regarding recursion is that if you're lucky enough to have your problem fit with tco (or have guarantees your problem is small enough), it's actually way simpler to both write and verify. write the base case, write the inductive step, translate to code, done. no hard reasoning about the code required because math. fact and fib demonstrate this nicely.
would prefer if programming languages offered a recursive decorator that would cause compilation or linting failures if tco isn't possible though.
It's hard for computers, too as it can easy result in stack overflow if there are too many steps.
non-flattened recursive algorithms spread their state across linear memory with a full stack frame for every iteration. at the least this defeats gains from caches, at worst it defeats them entirely by filling them with junk.
but, if the problem is small enough it can be worth it for the correctness guarantees and increased simplicity.
trivial iterative algorithms are simpler, sure, but when things get more complicated, recursion can replace very hard to understand, test and debug iterative code with simple recursive definitions where guarantees of correctness and termination come for free.
Exception proves the rule, though ;-)
Once they've "earned" the usage of the built in methods, they are tasked with rewriting them again, but this time without using any kind of looping. I give them a bit of time to think about how they may do this. Very few students get it but the plan is to live code it myself as an introduction to recursion. The task is still the same: to rewrite the map method. So the context for their intro to recursion is something they've become quite familiar with. It seems to have worked well.
See, to me, recursion is a kind of looping.
I have taught around 30 students over the past 12 years who went from knowing nothing to getting a SWE job and from my limited dataset I have observed that:
1. Students who were taught for/while loops first has a hard time grasping recursion. I suspect it is because following the recursive callstack gets tricky and feels unintuitive. This inspired me to try a curriculum where I teach students recursion first and don't expose them for/while loops until they are prepping for interviews.
2. Students who were taught recursion first has no problem understanding for/while loops when they were exposed to it.
For people who are curious, I used to teach students at my local library but recently created a free online curriculum at: https://c0d3.com
There have been various studies that agree with you: teaching recursion before iteration seems to have benefits! From my experience, it seems to be extremely unpopular among teachers due to the claim that recursion is "useless in industry". My personal opinion is that learning recursion first is probably a step in the right direction, but I'm curious to hear your thoughts (in terms of their claims of the usefulness of recursion in industry).
From my experience its true. I have never written recursive code in my 12 years of professional experience (primarily JS).
But during interviews, I have written recursive solutions many times to solve some of the harder problems.
68628933
* 26973931
=
(6862 * 3931) * 10000
+ (2697 * 8933) * 10000
+ (6862 * 2697) * 10000 * 10000
+ (8933 * 3931)
And then we notice that (6862 * 3931) can be expressed as (68 * 31) * 100
+ (39 * 62) * 100
+ (68 * 39) * 100 * 100
+ (62 * 31)
and so on for the 3 remaining products. And then each of these two-digit multiplications can in principle be subdivided into 4 1-digit multiplications.The reason the algorithm is recursive is that we're really talking about polynomial multiplication, which is convolution. (Other kinds of multiplication (e.g. dot products) are not convolution and this discussion doesn't apply to them.)
The above is interesting and curious, but is it useful? Yes. Karatsuba multiplication [0] exploits this recursion and adds some clever algebra to reduce the total number of multiplication instructions needed and thus speed up bignum multiplication.
For even bigger numbers, the Schönhage–Strassen algorithm [1] goes even further and uses the Fourier transform to convert the overarching convolution operation to element-by-element (dot-product style) multiplication.
[0] https://en.wikipedia.org/wiki/Karatsuba_algorithm#Recursive_...
[1] https://en.wikipedia.org/wiki/Sch%C3%B6nhage%E2%80%93Strasse...
a * (10*b + c)
= 10*(a*b) + (a*c)
In fact the usual method is even non-tail recursive, you build up a (literal!) stack of intermediate results which you sum back up at the end.for digit in a, for digit in b, multiply digits and offset by sum of indices of digits. The sun results.
They're a bit hard to write down otherwise.
a × b = 0 if b = 0
a × b = a + a × (b - 1) if b ≠ 0Isn’t this a horrific example for recursion? Wouldn’t most adults realise that the structure isn’t a tree (hi uncle grandma), and that it’s leaf boundaries are fuzzy. Or is that the point?
Apart from the fact that in a modern children’s classroom, bringing up parental discussions is going to be very complicated by other factors.
> These confuse recursion with cyclicity. If you don’t understand the difference, don’t use the dumb jokes.
Well, except cyclicity has a bunch of different meanings - I did a quick search and didn’t find a good reference for the sense they are using.
An actual family tree is not even a DAG because the nodes are unknown, and “parents” is an extremely fuzzy concept. Where does adoption fit it? What about step-parents? How do you record uncertainty? Etcetera.
One of my friends in another country had a kid with her first cousin, and parents can be more closely related biologically.
It is less than one hundred generations before we find a single common ancestor to everyone alive[3]. Also see [4][5].
Regardless, anyone that thinks using a family tree as a teaching example for children should have a reëducation holiday </unfunny-black-humour>.
[1] https://en.wikipedia.org/wiki/Tree_(data_structure)
[2] https://en.wikipedia.org/wiki/Directed_acyclic_graph
[3] https://www.scientificamerican.com/article/humans-are-all-mo...
The Little Schemer is a fraction of the size of HTDP and it’s focussed on this exact problem.
First of all, do not tell students to "start with the base case". The base case will be trivial and gives very little insight into how to solve the recursion problem. All the time they spend thinking about that is time spent not confronting the actual problem.
Beyond that, I think the way to teach recursion is to say "You cannot write the code until you can write a sentence in words describing the recursive solution. Your sentence must make reference to calling the procedure on a smaller sub-problem. Don't bother describing the base case in the sentence; it's boring - just fill it out when you write the code.".
So something like "The number of ways the robot can get to the last square is equal to the number of ways it can do it assuming it starts by going right plus the number of ways it can do it assuming it starts by going down".
http://www.npr.org/2003/10/14/167643282/google-founders-larr...
Starting at 13:45 it goes like this:
Terry Gross: Now I'll tell you, in preparing for this, I decided, let me Google Google, so I typed in "Google" into the Google search, and I came up with a lot of Google things in the regular search, but in the "Are you feeling lucky?" search, I got nothing.
Larry Page: Well you just got Google itself.
TG: Yeah, I just got Google itself. Oh, I see, Google was giving me itself.
LP: Yeah.
TG: Oh.
LP: In computer science, we call that recursion. [laugh].
TG: Oh, you even have a name for it. [laugh]. I didn't quite get that. I kept thinking it was just repeating itself. I didn't realize it was giving me itself. [laugh].
LP: [laugh]
TG: And what's the name for it?
LP: Uh, recursion. It's... kind of... Sergey is giving me a dirty look.
TG: Why?
LP: It's a loose definition. [laugh]
TG: Lighten up Sergey. [laugh]
LP: It's a loose interpretation of... [laugh]... recursion.
TG: Sergey, what's the more literal interpretation?
Sergey Brin: The technical term is you got itself back.
TG: Right?
SB: There's not really much beyond that. [laugh]
TG: Okay.
SB: Idempotence. How about that?
TG: Say it again.
SB: Idempotence.
TG: What is it?
SB: That's when you uh... [laugh]... Maybe I should stop while I'm ahead...
TG: ...You're just making this up, aren't you...
SB: ...Before I dig a deeper hole. Idempotence is when you do something and you get the original thing back.
TG: Oh, so that's a real word?
LP: It's a mathematical term.
SB: Yeah, yeah, but it's also just as loose an interpretation as Larry's was of recursion.
Though, there is also the Ackermann function... so for some recursive functions it is not be as easily seen why they would terminate.
Doesn't this work: consider the call graph of all possible argument lists (with an edge A->B if f(A) calls f(B)). If the function terminates, there are no cycles, so this is a DAG, which puts a partial order on the set of argument lists which strictly decreases with each call.
It's an easy to understand loop, which can be written as a recursive function of one argument.
Even though the possible arguments are just the natural numbers, nobody knows how to identify a suitable ordering relation which decreases with each call.
Yeah, if you know that the recursive function always terminates, then you know that each recursive call makes the set of remaining steps strictly smaller.
When you want to prove termination, then it is exactly what you need to show (that the remaining work does get smaller, no cycles).
Unlike a lot of people, I don't have this fear/anxiety about recursion. In fact the opposite. I friggin love recursion. I actively seeking out the recursive answer. It doesn't hurt my brain. In fact the opposite, it's gratifying on an almost sexual level. At some point in my apartment we had a recursion bin next to the recycling and normal trash. Inside of it was a power strip plugged into itself.
Maybe the trick with recursion is to teach it while the brain is still a bit plastic. Don't assume teens are too dumb. Talk to your kids about recursion, before it's too late.
1. Recursion 2. Teach your kids this entire comment
I disagree that factorial is not "applied" enough -- no matter what problem you choose, it will be interesting to some students and not others. The entire purpose of recursion is not speed, but to more aptly fit your mental model -- resulting in simpler code.
In that light, I am also surprised that the article did not explain that recursion is essentially solving a problem in terms of a simpler problem, e.g. n! = n*(n-1)!, which eventually becomes the trivial case 0! = 1.
It also helps if students have been exposed to the idea of recursion in math, for example by sequences where a_n is defined in terms of a_{n-1}, and/or proofs by induction.
Start by defining a node object (a person) which contains a name and a list of their offspring (other nodes).
Find if the given starting node or any of their descendants have a certain first name, or some other property.
Not the best or efficient line drawing method, but you can immediately use it for stuff once it's made, and using something that you understand how it works counts for a lot when you're learning.
Yeah, this is how recursion works. "If it ain't broke, don't fix it" maybe?
I don't really see why the author doesn't see the joke as recursive. If it's about the lack of a base case they're misguided. Maybe the author is distinguishing between functions that recurse by calling themselves directly (direct recursion) and ones that call themselves via other functions (indirect recursion). Or maybe it's about recursive functions vs cyclic data structures. Or maybe it's something else as neither of those explains why the quoted joke is incorrect either.
You'd think in a section titled "Recursion vs Cyclicity" the author would make some effort to explain the difference, but apparently that matters less to them than calling things "dumb".
A recursive definition is: "natural numbers are 0 or a natural number + 1". An (abridged) inductive proof that uses the recursive nature of natural numbers: "the sum of all naturals up to n is n(n+1)/2: 0*(0+1)/2 = 0, and (n+1) + n(n+1)/2 = (2(n+1) + n(n+1))/2 = (2n + 2 + n^2 + n)/2 = (n^2 + 3n + 2)/2 = (n+1)(n+2)/2 = (n+1)((n+1)+1)/2"
Note that this isn't enough by itself to support proof by induction.
Consider the definition of a list in Haskell which is analogous to the above definition of natural numbers: "lists are [] or a list with an element prepended".
Infinite lists satisfy this definition.
def sum(arr):
total = 0
for x in arr:
total += x
return total
and def sum(arr):
if len(arr) == 0:
return 0
return arr[0] + sum(arr[1:])
both operate over an array but I'd call one of them recursive and the other iterative.This also suffers from having a non-recursive solution, using bit-manipulation of a binary counter:
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);I always thought Euclid's algorithm had a pretty great runtime complexity, compared to "list all divisors, choose the greates common one". Is that not correct?
How Not to Teach Recursion - https://news.ycombinator.com/item?id=25610690 - Jan 2021 (9 comments)
Also computing the factorial is one of the first examples illustrating that recursion is useless in practice.
That variables can overflow is a valuable thing to teach, but it shouldn't be something the student bumps into during the lesson on recursion
(trees,permutations,graphs..)
Also it's a good illustration of how useless recursion is in practice.
The cause of failure in this case has nothing to do with recursion.
IMHO we don't teach compsci kids enough about the costs of things - new vs. * for example
It's also useful to learn that despite the love of recursion among computer scientists, in practice it's barely if ever a good idea.