Maybe that long disclaimer and digression about language wasn’t necessary, but I’ve seen too many battles over the meanings of all these words and I just wanted to set what I mean for the purposes of my writing. If you have your own definitions and preferred terminology that’s perfectly fine, just apply the appropriate linguistic transformation!
- Understanding control flow through an imperative program
- Spotting errors
- Knowing the format of objects that are arguments to callbacks used by library functions
- Knowing the format of an object returned by a library function
I want to talk about each of these in turn.
Seeing the flow of data in an imperative program is its own skill. It truly is. Once you throw in lexical closures with mutable state, the whole thing can get really tricky. What I tried to teach students to do is write down the chunk of code they were trying to understand on paper along with all the variables that were in scope as columns in a table. Then the rows of the table were execution steps and they try evaluating the code by hand updating all the variables in scope with each step of execution. This works for small snippets of code to give them the basic idea of what’s happening, but it’s not tractable for anything more than a few lines and a few variables at a time. It also all goes out the window if the code is calling other functions because then they have to go an manually inspect what side-effects that code may have. Being able to look at code and understand what’s happening and its control flow and what side-effects are involved at every point is an important skill and one that programmers in industry do need to develop! On the other hand, I feel like it’s a bit much to ask someone who’s trying to wrap their head around the very basics of how code executes to be able to keep track of all of these things in their head. I think it’d be more helpful, in this regard, to teach them something like Idris with its effect system or perhaps Haskell’s monadic way of isolating effects.
Yes, I know, there are plenty of people who would say both of those are too complicated for a totally newcomer to learn but I don’t actually buy that. Programming in an imperative language, if you haven’t done it before, isn’t the most intuitive thing in the world. It’s a very different kind of algebra, so different that it doesn’t really look at all like an algebra at all anymore, so you really can’t leverage the experience students already have with math classes all their life. Things like monads have, I think, a reputation for being so difficult to understand because it’s a very different way of thinking if you’re used to imperative and untyped programming. You basically have to unlearn what you know about how to structure programs. In my experience, though, the other direction is easier. When you’ve learned how to think of programs in terms of explicit state and effects and a more functional control flow, you can see the constructs of imperative programming for what it is. I wish I had some kind of large scale experiment on this but, sadly, the NSF isn’t returning my calls for funding on starting my own university system. All I have is anecdata, but I’m fairly confident in it.
The next two on the list were basically the same thing: untyped objects are just big opaque messes when you’re learning a library. Basically, you have to pour over documentation in order to try and figure out what an object “really is”, what methods it supports, and what these methods do. The documention is even pretty good for things like Node and the basic types that are included in the language such as arrays, but at the same time if you’re writing request handling for a Node server the documentations only goes so far in telling you all the things you can do with the request object that comes in. Essentially, the interface isn’t clear just by looking at sample code or docs. Improved documentation can help with this tremendously but, honestly, I don’t have control over who writes the documentation for the libraries we’ll be using in a class and trying to write enough learning materials to bridge the gap is extremely time-consuming. Typed languages, though, are self-documenting in way. Not so much that you don’t neeed well-written documentation (and again there’s the issue of confusing error messages), but enough so that if you were to look at a library on Hackage you can probably get a basic idea of what each function does just by skimming the types. I think this is a big advantage when teaching beginners. The last thing I want is for anything they learn to be an “incantation”, a piece of code that they think they need to put in but they don’t know why. I want the why and how to be as obvious as possible. Having a clear interface to every function and method would go a long way to reducing the number of incantations. Again, I’m not saying that well-described interfaces are inherent to typed languages nor that you can’t have really confusing types in a typed language, but I’m talking about the reality of learning to use libraries in your first language where the world isn’t exactly full of good documentation.
So what do I want to teach as a first language to beginning programmers? I’m not really sure. At the moment, I lean towards something like Idris but at the same time I have no real experience with teaching Idris to new programmers so I’m not sure. At the very least, I think it would allow me to explain the fundamentals of programming and the underlying algebra behind it in a more straight-forward way that students could carry forward into the kinds of languages that they’d use as professional programmers.