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
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!