This post is based on a hypothesis I had, that there is room in the ways we teach programming in between “start from the hardware and build up” and “put a bunch of high-level software pieces together” for an intermediate abstraction. I think this would appeal most to people who are learning programming for the first time, but are also interested in what’s happening behind the scenes. I’ve tried teaching a few people “how Python works” this way, and found the greatest success with people who tend to love getting into nitty gritty details to increase their understanding of a topic.
The model only has a few moving parts, and has the advantage that it’s simple enough to be executed by humans with pen and paper. I don’t think this is a fully-featured way to teach programming, or to teach how execution happens behind the scenes, but I hope it provides a good-enough high-level overview of both that it might satisfy the interested beginner with curiosity leaning more towards how execution happens.
In my experience, one of the best ways to teach somebody programming is to have them pretend to be a computer. This forces them to think clearly about what is happening at each step of a program, and to discover what some of the inner workings of the computer might be like in the process.
I think the fun part of learning how computers work is that they can do stuff. How many other objects can do stuff? Your refrigerator can do one thing (keep food cold). Your computer can play chess, or help you calculate your taxes, or show movies, or let you talk to your friends. That’s a lot of stuff for one thing to be able to do!
Too many programming introductions start with teaching all the different data types. It’s certainly good to understand ints and strings and lists and hashtables, and they do come up over and over again when programming. But this reminds me of the way too many people are taught multiplication: memorizing a table. Boring! I want to understand how computers do stuff.
Without motivation to memorize the multiplication table, it’s hard for students to understand what the point of it all is, no matter how much the teacher (correctly) insists that multiplication is an important skill and that having the basics memorized really will improve your life in some way. Being handed a list of stuff and told “memorize this” is hardly the most interesting or compelling way to learn something new, though.
A better way might be to introduce some problems. There are three rows of seats each with ten people in them. How many people are there total? Students can experiment with different ways to find the answer, and some might start recognizing a pattern the rest of us would call “multiplying”. Likewise, I think the right place to start learning programming is by sitting down and experimenting—writing tiny snippets of code and watching how they work. Make notes of anything unexpected, and update your mental models accordingly.
A good first programming language for many people is Python. Its high-level constructs and simple syntax are hugely helpful in that they get out of the way for new learners. In short, it helps you get to the doing stuff stage faster.
Rather than start by listing out types, let’s start with a basic execution model (the model of how stuff gets done) and see how that works. We want students to be able to directly follow along with what’s going on, so while the model won’t be an exact match for what’s going on under the hood, it should provide some insight into how execution takes place for the most basic Python statements.
Enter the REPL. There are four steps to the Python interpreter’s interpretation process of some statement:
It’s going to be super helpful for students to actually follow along here. I recommend using a service like repl.it to avoid all the pain of installation, and get right to coding. For now, we’ll only use the REPL box on the right. We’ll start with a very simple statement:
x = 0. First the student will pretend to be a computer and work out how this executes on pen and paper (or, my personal favorite, a giant whiteboard) and then we’ll double-check our guess against what the computer actually spits out.
Again, this doesn’t have to match up precisely with what the computer is doing here, but instead should provide a valuable mental model for the student to begin figuring out what the pieces a Python expression are. Use tables, Venn diagrams, or whatever whiteboard drawing makes sense here. Because I’m limited to what fits in a blog post, I’ll use a table.
We only want to explain the very basics here. (If the vocabulary is confusing, just drop it. Is “variables, literals, and operators” that much better than “names, values, and actions”?) Hopefully these should already be intuitive from math classes or are easy enough to explain. Variables are names for things. Literals are actual values that variables can have. Operators are the “verbs” which perform actions in our program.
That’s the reading step! Let’s move on to evaluation.
Now that we know what the pieces of this statement are, it’s time to evaluate. Just like in math, programming languages have an order of operations. Since our statement
x = 0 only has one operation, we’ll evaluate it first.
The rule for evaluating
<lhs>=<rhs> is to evaluate
<rhs> (the “right hand side”) first, and then assign that value to the variable on the
<lhs>. Our simple evaluation model currently has two steps:
Memory is a simple mapping from variables to the values they have. It might be helpful to draw this as arrows from variable names to values, for multiple reasons, but I’ll use another table here. Right now memory looks like this:
That’s right, memory is empty because we haven’t added any variables yet. Let’s move on to step two: expression reduction. Our current expression is:
x = 0
We want to first evaluate the RHS, so let’s reduce
0 is already a literal value, it’s fully reduced so we can move on to the second part of evaluating
=: assignment. We’re giving the variable
x the value
0, and the way we do that is by inserting those facts into memory, which should now look like this:
We’ve fully reduced the whole expression
x = 0 now, so we move on to the next step.
While the Python repl prints the values of most expression, there’s nothing to print for an assignment, so we move on here.
We’re done with the four steps! If you’re teaching a student one-on-one with a whiteboard, now is a good time for them to hand you back the marker to signify that control is moving from the computer back to the user, and the computer is now waiting for new input.
This way of executing statements by hand is definitely more laborious than just saying “
x = 0 assigns the value x to variable zero” but I think it’s also a lot more clear what’s really going on. The student has learned far more about how assignment works than they otherwise would have, which should lead to less confusion in the future. So many beginner questions are due to not having a sufficient model of what the computer is actually doing, which is what I’m trying to correct in the first place!
Let’s do another example:
x = x + 2 * 2
We read in the statement, so we can understand what the pieces that go into it are.
Memory currently looks like this, after executing
x = 0 last time:
The “precedence” (in quotes because I mean something slightly different here than the usual meaning) ordering of our operators is going to be something like:
+. Let’s start with
=. It first fully evaluates the RHS before assigning to the LHS.
The RHS is
x + 2 * 2. Again in precedence order, we either look something up in memory, or we apply functions and operators. Our highest precedence is
*, so we apply that operator to get a new RHS.
Our new RHS is
x + 4. We’ve reached a point where we can’t apply any functions or operators, so instead let’s look up the value of
x in memory. We see that the value of
0, so let’s fill that in.
Our RHS is
0 + 4. We can now apply the
The RHS is
4, which is a fully-evaluated literal value. We can finally do the assignment!
The assignment looks like this now:
x = 4. Let’s change the value of
x in memory to reflect our new assignment.
We’ve evaluated everything in this expression, so it’s time to move on to printing.
Again, assignment doesn’t print anything, so this step is easy!
Hand control back to the user!
We often have a chance to learn a lot from things going wrong, not just when they go right. This time, our user is going to be a bit silly and try to do
1 = 0. The student might balk at this if they know it’s eventually going to be an error. However, it’s instructive to understand which stage the error comes from, and how the computer figures out it’s an error in the first place. Errors aren’t magical, so we can use our same execution model to answer these questions!
We read the statement and break it down into its pieces:
For context, here’s memory right now:
We evaluate the assignment, which means evaluating the RHS first. The RHS is
0, a fully-evaluated literal value, so now we try to perform the assignment.
Here’s where things start falling apart. If we look in memory for the variable
1 we won’t find it!
1 isn’t even a variable. This is where we find out that this is an error. Our statement evaluates to some kind of error, which we can just call
When things go wrong, we definitely want to let the user know about them! Let’s print out
error and give the user a message that what they did doesn’t work.
Despite this error in the program, let’s be nice and give the user a chance to try again. We already printed out the error, so now we can loop back and ask them for more input.
I think this might provide a better foundation for how computers do stuff than the usual “let’s learn about integers vs. floats” intro lesson. Having students do the stuff themselves helps them better understand how the computer itself might be doing the stuff. Plus, now that there’s a model in place for how stuff gets done, it’s relatively easy to add on new stuff-doing techniques, and of course to eventually explain why doing stuff with strings is different than doing stuff with numbers.
Yes, there are a lot of rules. Yes, there is some inevitable memorization. But our model of the computer boils down to a blob of memory mapping from variables to values, an evaluation strategy (either lookups or applications), and a table that gets built when the computer reads a statement (to understand what each part of the statement means).
It’s almost trivial to introduce the fact that this four-step process doesn’t just happen in an interactive shell. The Python interpreter has another mode where it reads in a text file line by line, and does the four REPL steps to each line.
Finally, this mental model has one great advantage over merely learning data types or how to declare functions or whatever. It’s easy to experiment with. Take some unfamiliar Python statement. Try to run it by hand through this execution model and see what comes out the other side. That’s your hypothesis. Now, just go actually run that same statement in a real Python interpreter. This is your experimental result. Is there any difference between the two? If so, can you find it by breaking down the statement into smaller pieces? Is there any piece that might not be working the way you expected? Even if you can’t figure out exactly what’s going wrong, at least you’ll discover the right questions to ask.