The HOW and the WHAT
No, a low-level programming language is not defined by being closer to the metal. And high-level programming is not defined for being closer to English. Heck, BASIC was probably the closest to English, and you’d be hard pressed to defend that it’s higher-level than Haskell. Which is probably as difficult to understand for a native English speaker as any real programming language can get, with the sole exception of APL.
Anyway, indeed, some of those things are true. But they are only secondary attributes. They are like the fact that vi allows you to do much more in less keystrokes than any other editor: the core reason is that vi was designed to work over 300 baud phone lines, and this involved, among many other things, reducing keystrokes – but this was not the goal, but a side-effect.
Lower-level programming languages are created first, higher-level programming languages are created afterwards, in order to make programmers’ lives less painful. You know, programming is in some aspects very similar to being tortured: you have to keep dozens (if not hundreds) of things in mind while you write every line of code. And if you forget any of them, you write a bug – and you don’t know right away. Back in the day when the web wasn’t gobbling up all other platforms like The Nothing in Michael Ende’s The Neverending Story, you used C++ and the compiler would punish you with a basic warning or error for most cases, which is similar to getting a little pinch. Nowadays, it’s Javascript or Python, and you will be punished for the bug with a crash in front of your user or customer, which makes the minimum torture threshold more similar to getting a nail torn out.
But even if the driving force is improving the life of programmers reducing the cost of software development, the real way this is achieved is not by getting further away from the proverbial metal, but by allowing you to describe processes better. A higher level language allows you to describe things more succinctly, without having to get into the nitty-gritty details of every single excruciating little step. See a sample in assembly language:
1 2 3 |
MOV EAX, [first_value] ADD EAX, [second_value] MOV [result], EAX |
This calculates and stores the sum of two values. In three steps. Read the first value into EAX. Add the second value into EAX. And store the value of EAX in the result variable. Kids, this is how we did things back in the day.
And see this in C:
1 |
result = first_value + second_value; |
Thanks progress exists.
But still, even if this shows real evolution in programming, from a decidedly lower-level language to a higher-level one, it doesn’t show what the underlying key is. Removing the references to specific CPU registers and instructions is one step, but if we don’t understand the core, we can’t further this process and create even more useful programming languages. So, what is it?
In Turing’s spirit, all programming languages are equivalent in what you can do with them. But the truth is that they are not the same at all. Something like the SKI combinators are fully capable of any computation, and what’s more, it’s just three combinators, and one of them is redundant. See a sample program:
1 |
(S (S (K S) (S (K K) I)) (S (S (K S) (S (K K) I)) (K I))) |
(This piece above is number 2. Imagine how an XML parser written using the SKI combinators must look like.)
You could say SKI combinators are as far from the metal as you can imagine. Functional combinator application. Rewriting rules. Church numerals. No conventions, only a couple of basic core definitions. What not.
But even with this, I doubt anyone would dare call SKI combinators a high-level programming language. It’s about as low-level as it gets, similarly to assembly language. Ok, let’s cut SKI calculus some slack, at the very least, it’s probably the only calculus with its own Facebook page (http://www.facebook.com/pages/SKI-combinator-calculus/138648626160295 – and all of 6 people worldwide like it).
So, if closeness to the metal does not define the high/low level distinction, what does? Because it’s obvious that C and Java Fortran and Haskell share something useful and valuable that assembly language and the SKI combinatory calculus don’t. (Sorry, just kidding about Java.)
And here is the key: if you compare assembly and SKI, you see that most of the code you write deals with the nasty details of each step of doing something: reading a value into a register, or substituting a combinatory parameter in some way to rearrange information. While, on the other hand, the sample C code above depends on the underlying infrastructure, and the C code just lists WHAT the end goal is.
And this is the key: lower-level programming languages make you worry about HOW each thing is done. While a higher-level language allows you to get busy with WHAT to do, while letting the tools worry about how that goal is achieved.
Good stuff. I’m looking forward to disagree on it… it will be hard but let me try! 🙂
The WHAT you want to achieve with a program is (in theory) the same in any language, but the HOW is defined in terms of the primitives that language offers. Assembly has addition, KSI does not. C’s operations can be applied to arbitrary variables of a primitive type, Assembly’s only (in many cases) to a limited set of registers. C++ lets you apply operations to custom types rather than only the primitive (numerical) ones. Erlang lets you distribute a bunch of operations among multiple processes / machines.
In your Assembly / C example, the addition of these two numbers is presented as a WHAT, but in reality it’s just one step of a HOW solution to a larger ‘WHAT’ problem. It is very possible that if you were solving that problem in Assembly, the steps to solving it might not have involved creating these two variables.
Higher level languages don’t really change the focus from the HOW to the WHAT. They just change your current set of primitives for another, hopefully more sophisticated super-set, so that your HOW steps are easier to write, easier to understand, easier to modify, easier to run in different platforms, or (in general) measure better in some metric associated with the effort of the programmer. This is why the quality of a programmer’s code is largely independent of the programming language being used.
It is important to understand that, along with the new set of primitives in a higher level language, you often lose some primitives from the lower level language. It was a classic situation back in the days that writing 32-bit fixed point functionality was easier in Assembly than in C; JavaScript may be higher level than C, but good luck performing 64-bit bitwise operations in JavaScript. In general, if performance of the resulting program is part of the problem specification, high-level language constructs may be an impediment rather than an advantage. See for example the move towards Data-Oriented paradigms and away from Object-Oriented techniques in high-performance console games. (see https://plus.google.com/u/0/115950681746193428612/posts for more on this)
tl;dr In general, a higher level language makes it easier for the programmer to specify HOW to solve a problem, but it does not let the programmer focus solely on WHAT the problem is.
I love that “Data Oriented Design” page! Really interesting.
I agree with you that all languages are, in the end, equivalent, that it’s all a trade-off between sets of primitives, and that probably assembly language *IS* a better language for some tasks.
Still, my point is: I feel that a huge amount of the problems we solve nowadays in software are being solved using a very inappropriate set of primitives, which make our software worse and our lives more miserable. We need to advance our understanding to be able to build better tools. To me, software development is in its infancy, and things will get better as we advance. Our tools are terribly inadequate, and I miss a set of primitives that I feel is lurking below the current level of abstraction.
I know the race has no end, and that the axis of HOW-WHAT is actually a continuum, but advancing on that axis has to be the driving force to healthier software development.
BTW at some point I’ll discuss how I think there is no real distinction between CODE and DATA, so you can guess I have no problem accepting that HOW and WHAT are not completely disjoint concepts!
“You know, programming is in some aspects very similar to being tortured: you have to keep dozens (if not hundreds) of things in mind while you write every line of code. And if you forget any of them, you write a bug – and you don’t know right away.”
Dude – this is so going up on my wall. Love the series! I am working on this too…
Good interjection Jare on the Data Oriented stuff. I feel dataflow languages are a step toward the solution, but not the whole thing. (See for example Ptolemy from Berkely http://ptolemy.eecs.berkeley.edu/, others are Simulink from Mathworks [sucky because of the evolutionary/incremental history] and LabVIEW from National Instruments, but Ptolemy is more forward-thinking.) In the end, the dataflow stuff is like the functional stuff–you can go low- or high-level but you still get bogged down in the WHAT/HOW stuff.
But a real insight can be found in the SHRDLU program from Stanford back in the ’80s. I read about it in Douglas Hofstadter’s “Godel, Escher, Back,” chapter XVII (http://www.scribd.com/doc/6457786/Godel-Escher-Bach-by-Douglas-R-Hofstadter-) where I learned about the HOW/WHY tree, where asking “why?” moves you up a level and asking “how?” moves you down a level:
Dr. Tony Earrwig: By keeping track of selected parts of the original subgoal tree,
SHRDLU has some understanding of its own motives.
Jon, I similarly wanted to make this my own work, but may need to expand to work with others…seems you had some king of epiphany about this…
Gink, thanks for your feedback & ponters towards new material. I will try to find a gap to read them. I’m finding that sharing things helps me advance better, faster, realize mistakes earlier, and is all around more enjoyable. So I’m sure working with others will be more and more importnat in my work in the coming years!