I think we are on the track to create a perfect programmers factory.
Once someone understands (2), pointers are incredibly simple and obvious.
Math, as commonly encountered, is a collection of facts and relations. Their notion of variable is very different than in CS and programming.
To a mather (one who uses/does math), this makes sense:
x = 7 * y
y = 6
therefore x = 42
To a coder (of most conventional programming languages) that's an error, y is undefined when x is assigned a value. And even if it did have a value, changing it later wouldn't change x so who knows what x could be.It makes sense to the mather because each statement exists at the same time. To the coder, each statement is realized over a period of time. To the coder "y = 6" hasn't happened yet when "x = 7 * y". This is the part that has to be communicated to the novice with a mathematical background.
If the OP was teaching people a language like Prolog then there wouldn't have been the same confusion.
x <= y;
y <= y * 20;
These two effects actually occur simultaneously, which is different again from what mathematically inclined learners might expect since there is still mutation. It is more akin to the common notation: x' = y
y' = y * 20
Where the ' indicates the next value of x or y (as opposed to the derivative). Or perhaps: x_n = y_{n-1}
y_n = y_{n-1} * 20
And many programming languages don't even have a good notion for similar parallel evaluation and assignment. Python does, with: x, y = y, y * 20
Common Lisp has psetf which permits this: (psetf x y
y (* y 20))
Languages with pattern matching/destructuring bind are your best bet for writing this directly without needing temporary variables.Spreadsheets, expression-rewriting languages or any other functional programming language don't necessarily have this limitation, and declarations can be made in any order if the compiler supports it.
Mutability is really a result of the fact that computers are physical machines. So I think if you're going to teach software development, you should really start at the machine architecture and build up (ala NAND to Tetris) or start with an immutable-by-default language and only dip into mutation as an "advanced" concept, only to be used by experts and definitely not by beginners.
I endorse the second way, as I've seen it work very well, whereas I've seen more than one ship crash on the shore of understanding pointers...
Of course, my N is small.
x = f0()
x = f1(x)
He was just mentally blocked on how all of these things could be true at the same time, and/or on how a variable could be used in setting its own value, since assigning a new value to x would change the value that had been passed to f1! His intuition was that this meant some kind of recursion-to-stability was at play.All he needed was to be informed that this could be equivalently represented as
x = f0()
y = f1(x)
or x = f1(f0())you can just show them a ruler and ask them where does the first centimeter starts
The first centimeter starts at 0.
The first array element starts at 0.
(Also please note that both this and my previous reply are tongue-in-cheek).
"First" is an abbreviated form of "foremost". So if you start counting at 0, "first" corresponds to index 0. There's no real clash here yet; "first" and "one" are entirely separate terms.
"Second" is from Latin "secundus" meaning "following"; the second thing follows the first. So "second" corresponds to index 1. Again, no clash yet; "second" and "two" are basically unrelated.
"Third", though, actually derives from "three". So "third" has to correspond to index 3.
This leaves a gap, which might perhaps be filled by a new word "twifth", corresponding to index 2.
So now we have the cardinal numbers: zero, one, two, three. And the corresponding ordinals: first, second, twifth, third.
> you can just show them a ruler and > ask them where does the first centimeter start(s)
The question involves a nuance of the english language, the distinction between a measure of wholeness and the contents of that unit.
Length is a count of the completeness of a measure. The length of an interval must inherently include 0 as a unit to describe taking no units.
A 0 starting index is likewise also required to indicate skipping no storage cells.
Therefore an absolute index start must be zero based, and the Addition of duration must also include the option of zero as a means of describing a point at which to begin, but having not yet used any length.
The subtle trick is that is a point, an array of zero size; this is why the length, or alternately absolute stop point, for an array storing anything is one higher. It isn't actually zero indexed, it's a different property of measure (the size or stopping address).
A centimeter is also a wonderful example since it should graphically contain eight additional ticks for millimeter marks, and naught else, among it's inner span. This aligns wonderfully with explaining that there are targets spots for 8 bits of an octet within that single byte of indexed storage.
Though i do appreciate Fahrenheit for how it scales with humans. 0 is really damn cold, 100 is really damn hot, while in Celcius 0 is kinda chilly and 100 is instant death.
It's actually supposed to be the body temperature. Due to imprecise measurements, it's actually having a light fever. Flip note: C scales the same - 0 is the water freezing point, and then you have the same scaling as F but in 5s instead of 10s. [and you gotta remember: (f-2pow5)*5/9].
That's an old debate, yet if you are born/used to C, F is pretty horrific.
Also trampolines. Kids love trampolines. From there it's a simple jump (pun intended) to something like the Y-combinator!
Unfortunately teaching languages that are good for learning, like Scheme or Pascal, went out of fashion a long time ago. For years now universities have been teaching marketable languages. Ten years ago that was Java. Now it's Python.
FWIW I started with K&R, but times have changed and I definitely don't think it's a pedagogically sound way of teaching programming.
It never worked very well in practice. I expect that when you're fortunate enough to have unusually able teachers teaching unusually able pupils it might do OK -- but then, in that situation, anything will do OK.
(Tom Lehrer fans will know about this from his song "New Math", with "It's so simple, so very simple, that only a child can do it" in the refrain.)
I certainly had unusually able teachers.
Incidentally, that book has been praised for making computation easy to understand for non-programmers, which I would consider to be evidence for starting out low-level.
but times have changed
Not for the better, if you look at the average state of software today --- and I think a lot of it can be explained by the general lack of low-level knowledge.
All of them.
Any language that makes use of mutation for primitive constructs (not saying that's the case here specifically) needs an understanding of "observing/causing change" pretty early on.
One counter-example is Mathematica. I can mutate any object any time I like, and doing so is extremely common. Yet under "pointer" its help tells me about some unicode symbols, and some GUI stuff about mouse pointers.
In reality, some of its lists are dense memory like C, some contain arbitrary objects (such as expressions, or high-precision numbers), but these implementation details aren't part of the language, and most users never know them.
In Mathematica's case (and most other languages, sadly), presumably the concept of 'variable' is overloaded with the semantics of a pointer/reference. Meaning, you cause and observe changes to locations via a variable.
0-based arrays are based on hardware implementations; 1-based arrays are based on abstract collections; you could make the argument that 0-based arrays are a leaky abstraction, but the question then becomes: what abstraction? C explicitly tried not to abstract away the underlying hardware.
Surely the point of any language at all is to abstract away some details of the underlying hardware. Both for portability and to make things easier for human brains. How much you should abstract away obviously varies, based on what you're trying to do, and on how varied the hardware is.
And it depends on how much computation & memory you can give your compiler. One reason new languages can have nicer abstractions is that they don't have to compile on 1970s hardware.
Growing up with BASIC, I didn't have any understanding of pointers or how memory worked. PEEK and POKE, which I only saw used in other people's programs and examples, were black magic as far as I could tell. It wasn't until I learned C that I actually understood them but I still managed to write a whole lot of programs without ever using them.
To be a professional programmer I'd agree though. If you don't understand how pointers work, regardless of the language you're using, you're probably not good at your job.
It just makes a point opposite to the author's intent.
Thank you for pointing out my error.
You may not need to understand molecular differences, but knowing hows fats, proteins, and carbs work, along with how to substitute ingredients, and different theories of cooking all help to actually understand why you can cook until mysteriously things don't work the same as yesterday.
Of course, following a recipe in specific ideal circumstances works, as long as nothing changes.
And when beginners won't be beginners anymore they'l have to learn how the underlying abstractions work anyway. A programmer that doesn't at least superficially understand how operating systems work is not going to be able to write performant or secure software, let alome combine those qualities.
You don’t have to understand the low level details to be productive at the higher levels, you just have to understand the interfaces.
I've worked with programmers like that. They can glue a lot of things together, but when the slightest of problems appears, they are absolutely lost with how to debug. They usually resort to making pseudorandom changes until it "appears to work", and are also unable to appreciate just how fast computers are supposed to be.
The kind of understanding you're rooting for relies on formal logic, not electronics.
We're all dealing with abstractions over abstractions at this point, so it's really about finding what model of understanding works for the learner and then going from there.
Instead of just graduating 50 people who "get the trade", we're now diluting them with another 350 that "made the grade". Then tech firms need wild, crazy interview processes to sort them back out. Everybody suffers, including the 350 who may have been productive and happy in another, more suitable profession.
It's less about gatekeeping, and more about sustaining a healthy industry that is quickly headed for a disastrous race to the bottom. Google and friends are spending billions under the magnanimous "bringing coding to everyone" when they really just want to lower developer salaries. Not everyone needs to learn how to code, just like not everyone needs to be a doctor, or learn how to be a carpenter. I constantly wonder when will be the right time to "get out" before salaries start their inevitable side downwards. Could be a couple decades though.
The idea is that if the hard stuff comes too late, people might not find out that they aren't really suited to that field as a career until it is too late to change majors without having to take an extra year or two of college.
The intro class is too early for that, though, because CS is useful enough for other fields that many people (perhaps even a majority of people) taking intro CS don't intended to actually go into CS. Better is to put it in the first class in a field that will mostly only be taken by people who are intending to go into that field.
Just like DRM, make it a little harder to skate on by and you actually filter a lot of casual/non-serious people.