views:

266

answers:

7

As far as I can see, the usual (and best in my opinion) order for teaching iterting constructs in functional programming with Scheme is to first teach recursion and maybe later get into things like map, reduce and all SRFI-1 procedures. This is probably, I guess, because with recursion the student has everything that's necessary for iterating (and even re-write all of SRFI-1 if he/she wants to do so).

Now I was wondering if the opposite approach has ever been tried: use several procedures from SRFI-1 and only when they are not enough (for example, to approximate a function) use recursion. My guess is that the result would not be good, but I'd like to know about any past experiences with this approach.

Of course, this is not specific to Scheme; the question is also valid for any functional language.

One book that teaches "applicative programming" (the use of combinators) before recursion is Dave Touretsky's COMMON LISP: A Gentle Introduction to Symbolic Computation -- but then, it's a Common Lisp book, and he can teach imperative looping before that.

+2  A: 

I have never seen this order used in teaching, and I find it as backwards as you. There are quite a few questions on StackOverflow that show that at least some programmers think "functional programming" is exclusively the application of "magic" combinators and are at a loss when the combinator they need doesn't exist, even if what they would need is as simple as map3.

Considering this bias, I would make sure that students are able to write each combinator themselves before introducing it.

Pascal Cuoq
+6  A: 

IMO starting with basic blocks of knowledge first is better, then derive the results. This is what they do in mathematics, i.e. they don't introduce exponentiation before multiplication, and multiplication before addition because the former in each case is derived from the latter. I have seen some instructors go the other way around, and I believe it is not as successful like when you go from the basics to the results. In addition, by delaying the more advanced topics, you give students a mental challenge to derive these results them selves using the knowledge they already have.

AraK
They don't introduce exponentiation before multiplication -- but they certainly explain addition before the Peano axioms. (And that's a much closer analogy to the question.)
Eli Barzilay
I think your analogy is correct when talking about multiple levels of abstraction. In the same level of abstraction, my opinion is that you should go bottom-up.
AraK
So you *would* teach Peano axioms in grammar school? Good luck with that!
Eli Barzilay
@Eli Of course not! What I meant is that I agree with you when talking about which level of abstraction should we start with. I would start with the highest level of abstraction because that one is more accessible for someone who hasn't been exposed to the subject before. On that level of abstraction, go from the lower levels to the upper levels. I hope I am clear by now. What you are saying is like we should teach transistors before even thinking about introducing logic gates! What I said, is that if you are teaching logic gates, then teach students AND, OR and NOT before introducing XNOR.
AraK
@AraK -- that makes sense then, I misunderstood you earlier.
Eli Barzilay
+3  A: 

I also think introducing map/reduce before recursion is a good idea. (However, the classic SICP introduces recursion first, and implement map/reduce based on list and recursion. This is a building abstraction from bottom up approach. Its emphises is still abstraction.)

Here's the sum-of-squares example I can share with you using F#/ML:

let sumOfSqrs1 lst = 
    let rec sum lst acc =
        match lst with
            | x::xs -> sum xs (acc + x * x)
            | [] -> acc
    sum lst 0

let sumOfSqr2 lst = 
    let sqr x = x * x
    lst |> List.map sqr |> List.sum

The second method is a more abstract way to do this sum-of-squares problem, while the first one expresses too much details. The strength of functional programming is better abstraction. The second program using the List library expresses the idea that the for loop can be abstracted out.

Once the student could play with List.*, they would be eager to know how these functions are implemented. At that time, you could go back to recursion. This is kind of top-down teaching approach.

Yin Zhu
The comparison doesn't stop at "more abstract / too much detail" though. The second implementation also allocates an entire list that it throws away immediately. It would worry me to teach students to do that. The best students would notice and despise the language you are teaching for not allowing to do things elegantly. The others would not notice and subliminally learn that it's ok to duplicate structures without even thinking about what they're doing.
Pascal Cuoq
@Pascal. Good point. However, in my opinion, every piece of knowledge has several facets. For the `List.map` thing, at the first it is introduced to the students, they may just use it as a high-level abstract. Later, when all the details are taught, they could come back to the usage in the example and know the inefficiency issue. It is very hard to learn one thing thoroughly on a single visit.
Yin Zhu
+3  A: 

There is something fundamentally flawed in saying "with recursion the student has everything that's necessary for iterating". It's true that when you know how to write (recursive) functions you can do anything, but what is that better for a student? When you think about it, you also have everything you need if you know machine language, or to make a more extreme point, if I give you a cpu and a pile of wires.

Yes, that's an over-exaggeration, but it can relate to the way people teach. Take any language and remove any inessential constructs -- at the extreme of doing so in a functional language like Scheme, you'll be left with the lambda calculus (or something similar), which -- again -- is everything that you need. But obviously you shouldn't throw beginners into that pot before you cover more territory.

To put this in more concrete perspective, consider the difference between a list and a pair, as they are implemented in Scheme. You can do a lot with just lists even if you know nothing about how they're implemented as pairs. In fact, if you give students a limited form of cons that requires a proper list as its second argument then you'll be doing them a favor by introducing a consistent easier-to-grok concept before you proceed to the details of how lists are implemented. If you have experience teaching this stuff, then I'm sure that you've encountered many students that get hopelessly confused over using cons where the want append and vice versa. Problems that require both can be extremely difficult to newbies, and all the box+pointer diagrams in the world are not going to help them.

So, to give an actual suggestion, I recommend you have a look at HtDP: one of the things that this book is doing very carefully is to expose students gradually to programming, making sure that the mental picture at every step is consistent with what the student knows at that point.

Eli Barzilay
I do note that HtDP teaches recursion before combinators, although it introduces a custom data type for positions before teaching lists.
Nathan Sanders
Well, I didn't express an opinion about the actual map vs recursion, just on the blind belief that "more basic" = "batter teach earlier". In any case, HtDP teaches recursion in a very methodical way: note that unlike practically all books, it doesn't start with `fact`, because structural recursion is much easier to understand. (Recursion on numbers is also structural, but that's a very non-obvious structure.)
Eli Barzilay
+1  A: 

I think this is a bad idea.
Recursion is one of the hardest basic subjects in programming to understand, and even harder to use. The only way to learn this is to do it, and a lot of it.
If the students will be handed the nicely abstracted higher order functions, they will use these over recursion, and will just use the higher order functions. Then when they will need to write a higher order function themselves, they will be clueless and will need you, the teacher, to practically write the code for them.
As someone mentioned, you've gotta learn a subject bottom-up if you want people to really understand a subject and how to customize it to their needs.

Rubys
A: 
Pessimist
I think you have that a bit backwards: "loops" are just a recursive function where the programmer has applied tail call optimization by hand. I don't think we want to *encourage* that--isn't applying trivial obfuscating optimizations what we have compilers for? General recursion is more powerful anyway, which is why you get people who overuse tail recursion reinventing the wheel by creating their own pseudo-call stack instead of just sensibly using recursion.
camccann
Maybe you can think of "loops" as being a "recursive function" if you're looking at it theoretically. However I'm talking about how it gets translated to machine code, and tail-recursion does get translated to JMP, while a "for" loop in C will not get translated to function calls in assembly. So when you say that a loop is just a recursive function, you have to qualify that you are seeing it from the academic POV, not for what it really is. General recursion causes stack overflow; it is not something anyone should be using.
Pessimist
What the compiler spits out is implementation-dependent; if a C compiler produces really terrible binary code for something, you either find a better compiler or you work around its flaws. General recursion is the most natural way to express many algorithms, and is *exactly* what people should be using, not reinventing the wheel by building a second stack on the heap. Don't let the limits of the tools you use limit the thoughts that you think!
camccann
I agree that general recursion is the most natural way to think about many algorithms. Unfortunately I don't know of any language that has a compiler that will translate your recursive code into something that doesn't overflow the stack (like translating it to tail-recursion).
Pessimist
A: 

I find that a lot of people when programming functionally often 'imitate' an imperative style and try to imitate loops with recursion when they don't need anything resembling a loop and need map or fold/reduce instead.

Most functional programmers would agree that you shouldn't try to imitate an imperative style, I think recursion shouldn't be 'taught' at all, but should develop naturally and self-explanatory, in declarative programming it's often at various points evident that a function is defined in terms of itself. Recursion shouldn't be seen as 'looping' but as 'defining a function in terms of itself'.

Map however is a repetition of the same thing all over again, often when people use (tail) recursion to simulate loops, they should be using map in functional style.

Lajla