Teaching abstraction

I’m just beginning to prepare for the third incarnation of CIS 194, Introduction to Haskell in the spring. It’s occasioned some general thoughts on teaching abstraction which seemed worth writing down.

Abstractions, of course, are everywhere in CS. By abstraction I mean anything with multiple “levels”, where upper levels hide details of lower levels. An operating system abstracts over the details of hardware; objects abstract over their internal state; functions abstract over their implementation; and so on. Let me just state up front my main thesis:

Students should be explicitly taught to think on multiple levels of abstraction.

Stated like that, perhaps it seems obvious; allow me to elaborate.

On the face of it, when learning about some abstraction, there are two things one can learn. One can learn how the abstraction is implemented (that is, the details of the lower level); and one can also learn how to use the abstraction (that is, how to work with the upper level while ignoring the details of the lower level).

In many cases, students already know one of the levels and learn about the other. For example, once they get around to learning about how compilers work, students already know how to write programs. Or perhaps they learn both levels as part of the same course, but at different times: for example, they might first learn about logic gates, and then later move on to talking about latches and adders.

It’s when teaching both levels at once that one must be extremely careful. Such a situation might arise, for example, when teaching a concept that is completely new or difficult to motivate—since it’s best to start with the concrete and move to the abstract, one might wish to begin with the lower level, but referencing the upper level is necessary to motivate the lower level in the first place. The problem with such a situation is that it is really easy for students to get the levels confused.

In particular, I have in mind the Applicative functor abstraction (and Monad as well). The last time teaching CIS 194, I didn’t do a very good job with this. I gave my students a homework assignment which had them first implement an Applicative interface to parser combinators, and then use it to write some parsers. Most students missed the point, however, and kept on using low-level implementation details when writing their parsers, instead of working in terms of the Applicative interface.

It’s tempting to just say “don’t do that”, i.e. don’t confuse students by teaching multiple levels at once! But I think the situation actually presents a great opportunity. In the “real world”, one seldom has the luxury of thinking on only one level at a time. Being able to explicitly switch between levels, and keep multiple levels straight, is an important real-world skill. I think the real solution is to explicitly teach students to think on multiple levels of abstraction: that is, be explicit about the fact that there are multiple levels, and teach them to consider carefully at each point which level they are thinking on, and why. I plan to do this in the spring and will report back on how it goes!

About these ads
This entry was posted in teaching and tagged , , , . Bookmark the permalink.

8 Responses to Teaching abstraction

  1. Ryan Yates says:

    Yes! It is often hard for me to realize the levels and boundaries that my own thinking is respecting.

  2. tikitu says:

    You might consider deliberately letting them make this mistake as a step in the learning process. If a later assignment lets them reuse this code but only if they separated layers cleanly, the firsthand experience will likely stick much better than abstract arguments. (It’s very difficult to stay *fair* with this kind of trickery though!)

    • Brent says:

      I do appreciate the value of letting students learn from their mistakes, but I can’t think of a way of doing this that wouldn’t be discouraging for the students. I am not a big fan of setting things up with the intention of having the students make a particular mistake in order to learn something (unless it is fairly small and immediate).

      • tikitu says:

        When you put it like that, my suggestion sounds rather ugly. (If I wanted to defend it I would push the difference between *intending* mistakes and *expecting* mistakes; but I think really I just overstated my case.) The idea is maybe partially salvageable, though: say, Assignment 1 is “make this work by any means possible”, then you discuss levels of abstraction in class, then Assignment 2 is “rework A.1 keeping levels of abstraction in mind”, then Assignment 3 is “do X which will only work if you got A.2 clean enough (fixing whatever you need to fix along the way)”.

        While discouraging students is of course a bad idea, I do think the frustration of dealing with leaky abstractions is somewhat central to a real understanding of why we try to avoid them.

        Anyway, I’ll be very interested to see how your Spring experiment goes!

  3. I often try to make this distinction between levels clear to my students. But one thing to beware of, especially if you’re teaching freshmen, is that you need to be sure to motivate the simultaneous need for both levels of abstraction. Younger students, so used to being told there’s only one level, all too often take the simultaneous presentation of two levels to mean only one of them is the “real” level and the other is some distraction you’re bothering them with (which is which depends on the student). So, for teaching Haskell, it might be good to start with the analogy of programming languages designed for humans to use vs their implementations designed to be run by computers; and then proceed to complicate the notion of the boundary between human and computer. Having an assignment where you make them write the “assembly” to solve some problem before giving them the abstraction that makes short work of it could be another tactic. In any case, definitely keep the motivations clear for why both levels are necessary/helpful. Monads are a great example here since Haskell allows polymorphism over the specific monad, so you can show them how the abstract notion allows reusing code by ascribing homomorphic semantics to it. If you have time and the students are up to it, it might be good to cover performance differences between different implementations of the “same” semantics: e.g., lists vs LogicT, or free monads vs codensity.

  4. Time for your promised report, isn’t it?

    • Brent says:

      It is indeed! I will try to find time to write up a few thoughts on this soon.

    • Brent says:

      Well, I’m not sure I have enough to say for a whole blog post so I’ll just report here. In short, I changed two things from last year: (1) I spent some time in class explicitly talking about levels of abstraction, as discussed in my post above. It’s hard to know to what extent this influenced the students and their thinking but at least they didn’t just give me blank looks. (2) I spent two weeks on Applicative instead of just one, which also meant splitting the homework assignment in two. The first week, I motivated the pattern and then we just spent some time implementing instances; the homework had them implement a somewhat more involved instance for a parser combinator type. The second week, we talked about programming against the Applicative API, building combinators like sequenceA and so on; on the homework they implemented a parser for a simple grammar. As far as I can tell this went much better than last year, though it’s hard to tell which of (1) talking about levels of abstraction (2) splitting the homework assignment (3) spending more time on Applicative had the biggest effect.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s