This is, I think, an experiment with this forum. It feels like a meta discussion to me, rather than a true question for the main site. It also gives me an opportunity to state some things about my teaching philosophy that some will consider opinion. It is, in fact, an example of something that might be a blog entry if such can be made to exist here.
First, to answer the question directly, no, it is not necessary to map concepts in a high level language (say, Java), to a lower level language or system (say C, or machine language). Compilers do that. Students don't need to and, in my view, should not be taught that in the earliest course. Doing so adds an unnecessary burden, as I'll try to explain as this goes on.
If I were discussing C, I would feel differently, of course, since C itself was conceived as a light abstraction layer over a particular assembly language. The reason that s++ exists in the language is that it is a direct translation of a machine language instruction for the PDP-ll (well, technically, a PDP-7, I guess). Since there is so close a relationship between C and assembly language it is more natural to think about the machine itself. But the same is not true of a language even as similar to C as Pascal, and certainly not Java, much less Scheme. I will try to expand and explain these conclusions below.
When a modern language designer builds a new language, they have it as a goal to build a complete self-consistent model of computing that can be (somehow) reduced to a Turing Complete language. I.e. it is capable of computing any computable number. Even languages that aren't intended for general computation usually wind up Turing Complete. Some versions of SQL, it seems, are TC.
However, being TC only states what a language is capable of, not how a programmer in that language needs to think. It is that "thinking process" of the programmer that I want to focus on. How should a beginning programmer be taught to think in Java to become a professional level programmer, whether or not being such is their goal?
First, an aside about machines. Computers today are themselves built on a stack of abstractions, from the silicon upwards to something we think of as an architecture. The simplified view of a computing machine today is not, in reality, actually implemented that way. The common view is that there is a homogeneous array of cells of a certain size (say 32 bits), a Program Counter, an execution engine, and a Stack. Simple instructions may be stored in the memory, retrieved (from the PC location) to the execution engine and there executed, modifying memory or other devices. In reality, what you think of as say Intel Architecture, is itself implemented as a program (micro-program) on some simpler machine that may have little or no resemblance to the architecture you recognize The micro-program is translated, by a compiler, into instructions for that simpler machine. Memory alone, today, is very complex, with multiple layers of cache and even different sorts of cache for instructions and data. The actual "hardware" that you execute on bears little resemblance to the low level "mental model" that many use to explain things to students. In reality, that model is just another abstraction, not a concrete thing. It is a mental model that many feel is useful for students to understand, though I do not - in the first course. Later, it will be useful for students to see these abstraction layers, since, if they study CS, they will eventually be asked, to know something about compilers and interpreters, for example. They may also be asked to design new languages. Likewise, I think students should eventually learn something about transistors and their relation to binary operations, but in seeing some first program that includes, for example, assignment, students don't really need to know how a transistor works or even how a memory cell might be updated.
Most students don't need to know that the "stored program" computer disappeared a long time ago, in fact, when operating systems started to build a wall between programs and data, and we learned that self-modifying programs, while fun, are usually impossible to work with. In particular, there is no record left in a self modifying program of what was actually executed. Likewise, I will take a wild guess that most of you don't know about the hidden-bit in IEEE floating point unless you teach the Architecture course. Nor do your students need to know about it until that course.
What students do need, however, is a self-consistent model of computation in which they can effectively think. That model need have no necessary relationship to any other model as long as it is (a) complete and (b) consistent.
Where to Start
The first thing to remember here, is that I'm teaching novices, not experienced programmers. In a first programming course, I want to teach them to be good programmers along with a few other things that may ease their way later in studying the broader CS field. But, once I choose a language, say Java, I want them to be good Java programmers. Therefore I need to look at good Java programs and also at what the language designers were trying to do when the language was created.
Ages ago, the language was Pascal and the key idea of the language was to enable top-down problem decomposition; envisioning a problem as a composition of simpler problems which after repeated decomposition can be simply solved as procedures and then the solution procedures recomposed into an overall solution. That is no longer the case in an Object-Oriented language like Java.
In Java, a program is envisioned as an interacting set of independent objects that communicate with message passing. A programmer both builds these objects and sets up a chain of messages to solve the original problem. So that is what I want to teach. How do we build objects? How do we set up the message chains. Normally I begin with the second problem first, giving the students some simulation in which they can solve (perhaps not conveniently) sophisticated problems just by message passing. The objects themselves are those with characteristics I want them to eventually build, but they are opaque initially. However, the objects that are built from the given classes have public methods and nothing more. The objects are "bundles of behavior." There is no inherent notion of state other than which object is currently executing a method. The classes given to the students are not shown in source form, but only through documentation (java docs). But the system needs to be rich enough that "interesting" problems can be solved.
In this scenario, the only variables that the student sees are references to objects. The first half of the first course need have no mention of primitive data at all, other than, perhaps, strings for tracing behavior. Additionally, the course discusses interfaces and building objects by composition rather early. A class is built and if it has fields at all, then those fields are references to other objects.
One thing I'm not as happy about as I'd like to be, however, is that my course, if I'm not careful will put too much emphasis on inheritance rather than composition. In my view we made two mistakes in teaching OOP early on. One was over-emphasizing reuse and the other was overemphasizing inheritance. In reality, OO is better thought of as enabling the construction of interesting things via composition. Reuse usually gets in the way and leads you to try to build "reusable objects" rather than "useful objects, increasing cost." Likewise inheritance is too often used to save a bit of typing, rather than to build the (rather rare) pure specialization hierarchies for which it is best adopted. It is also often badly used in situations that don't really involve conceptual specialization. One of the worst examples I've seen of inheritance is making Cylinder a subclass of Circle, since the Circle class already has a radius field. But the specialization is exactly backwards here.
So, a good Java program, in my view has a lot of interfaces and a lot of classes implementing those interfaces, but not extending one another. The composition of an object is mostly from other simpler objects, rather than primitive values. The student will first see integer variables and arrays and such in the second half of the first course, after they have already developed enough skill to write nearly any program without them. The course is syntax poor, in fact, but requires a lot of deep thought into problem decomposition.
The main implication of this is that students program in a Java-centric world. All of the concepts they have are "larger" Java ideas (interfaces, classes, methods, immutable objects, ...) rather than smaller concepts. That is combined, of course, with the abstractions that they build themselves when they write a class. Once they write a class (bundle of behaviors) they treat its objects as primitives in that web of messages to solve a problem.
The Mental Model
The Java Programming Language made a lot of compromises in its creation. Not everything is an object, for example. Ruby comes closer to the ideal, but it isn't perfect. In Scala, however, everything, even methods, are objects and can respond to messages. I normally treat Java as if it is purer than it really is, since it gives me a simpler mental model when I work (I write a lot of code) and when I teach.
The main thing that I haven't yet discussed is a consistent model for computer memory. How does a computer retain information? Some people start their discussion about a lower level computing model with something like:Simple model of a computer. I don't. Nor do I discuss anything similar to that in the first course.
A pretty good place to start this is with the a poem from Through The Looking Glass, the sequel to Lewis Carroll's Alice in Wonderland.
- The song's name is called Haddocks' Eyes
- The song's name is The Aged Aged Man
- The song is called Ways and Means
- The song is A-sitting on a Gate
See wikipedia for a discussion and background as well as the poem itself.
In this view, a "variable" is just a name that references a value (usually an object and in some languages always an object). But the important thing about the variable is that it is just a name. You have a name, say Bob. The name "Bob" refers to you but IS NOT you. Only you are you. Your name might be Bob, but you might be "called" Bobby, though that isn't technically your name. It isn't normally done, but your name might be "called" something. Maybe your name is called "moniker." That gives the four levels from Carroll's poem, though in computing we usually only need (the last) three of them: the name, the thing, and what the thing is called. A variable is like a name. A thing is like an object. What the think is called might be thought of as any alias for the name. That analogy isn't perfect, of course.
So in Java
says that we will use a name x to "refer to an int". According to the rules of Java it will refer to 0, in this case, though you normally give the original reference in the declaration:
int x = 42;
Now we have a name and a value to which the name refers. There is no need for any lower level "machine" or mental construct. Your students, understand names and have since near birth.
An assignment, such as
x = x + 3;
consists of two parts. On the right we have the evaluation of an expression, resolving any names to the values they represent. On the left we have a re-assignment of a name. The name now references some value other than the one it did before. Nothing more. Your students understand this also. They understand that at different times in a given conversation the name "Bob" might refer to two different values, you and, say, your father. The naming is consistent at any given time. But at different times a name can refer to a different value.
Likewise aliases are well known to your students. Assigning a new variable (i.e. name) to an existing value, makes it an alias. Assuming "buddy" has been declared:
buddy = bob;
makes perfect sense. Two names for the same object. If they are objects and I send a message using one of them (bob.jump();) while observing buddy, we see that buddy has responded to the message since there is only one value. But there is only one 42, also, if you are at all math inclined.
So, variables are nothing more than references to values. In Scala it is actually a completely factual statement. In Java it is a myth, but a useful one, since it unifies the way we think about primitives and objects, rather than bifurcating it. This simplification of thinking is useful, and since consistent, won't lead you astray, until you try to write a compiler for Java. But that will come later and there is time for a more "physical" view of computation before then.
On the Consistency of the Model and Staying at One Level.
More is needed. I will very briefly mention many of those things, but I'm sure I won't remember all of them.
Classes are written so that the users don't need to know anything about the implementation to use them. Documentation of public methods is essential (java docs). The methods of the class give away nothing about whether something is stored or not, unless the concept itself (set, map, ...) requires it. All public names are "intention revealing." Actually I'm paranoid about names and do this even for naming in one-off programs I write for myself.
Coding effectively, using composition in preference to inheritance, requires a few important standard design patterns. For me, Strategy is the most useful. If two objects are almost the same but exhibit some different but similar behavior, I'm inclined to represent the behavior as a Strategy object held within and each such external object executes its own strategy. This also lets me minimize the use of if statements and hence make my individual methods short sequences of simple statements (not always, but often enough to make a difference).
Write a lot of interfaces. No subclass of another should extend the public interface of the superclass. It should be identical. If you make such an extension you aren't programming by specialization (the real purpose of inheritance), but by modification. This makes it necessary (far too often) to know the specific type of an object. If you don't extend the (informal) interface of a class you don't ever need to cast. If you need a richer protocol, write a new interface, perhaps extending an earlier one, and implement that, rather than just implementing the old one and extending.
Show Students Real Code I tend not to show students classes in Java that do little more than illustrate some syntactic point. These usually have completely abstract and meaningless names, just to get some minor point across. In fact, I'm much more likely to show them a somewhat sophisticated program in which I've uses something in the language they may not know. I may not have to say anything about it, as it may be obvious to them (or most at least). I'll explain it if asked, of course, or if evidence suggests I've missed something essential. But I think we spend too much time as teachers talking about things already obvious to our students. Unfortunately, I think, too many of the APCS questions and exercise have that syntax gotcha tricky character to them, rather than focusing on real programming.
In other words, write your code so that you create a consistent myth about the program and so that users of the parts don't need to try to map that myth to some other.
Why It Matters
If I try to cast the things I'm teaching in Java into a lower level model that I think they will understand better, I'm probably wrong. My own computing history took me through all of those levels, since the simple model was actually pretty accurate when I began. But my students don't have to recapitulate my history nor that of the computing field to understand today's languages, systems, and problems.
In fact, when I do that, my students have to understand two models, not one, and either of them could be confusing. Moreover, they need to understand something of the mapping between them in order to understand how a "higher level" construct can be cast onto a "lower level" one. So, in my view, I'm adding to their burden by trying to map levels. Later they can see that, of course. I'm not trying to hide things, but only to teach a (a) complete and (b) consistent model of computation that new learners can grok directly. By keeping them at one level of thinking I can make it as simple as possible.
None of the above, however, is meant to imply that teaching should be in any way at an "abstract" level. In fact, I find metaphor and analogy essential to teaching. But I don't try to push a metaphor to the point at which it becomes an alternate model with an implied mapping. Often, even usually, a new topic is best introduced to students by giving them a concrete example, either from the programming world or from their everyday experience. You can, for example introduce the concept of a database as being "similar to" a box of index cards. The analogy isn't very close, but it is a place to start. An array is "like" a to-do list. Position is important. Re ordering is difficult.
But in using analogy you also need to be explicit about the limits of the metaphor. An array is "not like" a to-do list as it is easy to add something to the end in the latter case.
Using metaphor is not the same as mapping language constructs to a lower level model. It is just a way to get a "hook" into student thinking to point them in a useful direction, even when the metaphor is incomplete.