So much work to do! We’re pretty much rebuilding Ladon from the ground up in the first months of 2016. I’m excited.
I had a nice conversation this morning with someone interested in learning game programming. He asked me a good question: “Can all programming languages do the same things?” That’s a pretty important question and the full, “true” answer probably lies hidden underneath many layers of mathematical theory. The short answer that I gave him was “Almost.” I think that’s a fair answer, but I decided to elaborate in a blog post, since the real answer is very complicated and might be interesting to anyone looking to be a programmer.
First, a disclaimer: I’m not a professor of computer science or any kind of authority. I’ve been programming for about 35 years, so that’s what I have to use to form my opinions. But I will say that I’ve just about seen it all, as far as programming is concerned. I’ve written a little toy operating system. I’ve written a few compilers and a couple of these have ended up in real, commercial products. So there.
There’s an idea in computer science/math/philosophy called “universal computing” or “Turing completeness.” If you’re the geeky type, then the answer lies there. There are lots of books written on the topic and Wikipedia’s page on the subject is probably the place to start. Ok, now, if you’re not into formal proofs and rigid mathematical definitions, but are still curious about the idea of one programming language versus another, then the rest of this post is aimed at you.
At the highest level where most of us interact with a computer, we click on something with a mouse and some action happens — a game runs or a spreadsheet opens or a song plays. Well, all of those “actions” that happen are computer programs running. In fact, most of those things involve many (dozens, maybe hundreds) of programs all running together. Lots of them are part of the operating system, like the program that reads your mouse input, the program that runs your graphics card, and so on. These little helper programs are called “drivers.” You have a “mouse driver” and a “keyboard driver” and a “graphics driver” and a lot of other drivers running all the time.
At the heart of what you actually want to do, though, there’s a program called an “executable.” This is the game itself or your spreadsheet program or your web browser or whatever it is that you’re actually interested in doing. You can see these executables scattered all over your hard drive. They end with the extension “EXE,” which is short for “executable.” If I look at the folder on my hard drive where Ladon is installed, for example, I see this:
There it is, third item in the list, the actual executable for Ladon. So what’s inside that file? Well, let’s take a look and see.
That’s the executable file opened up in a text viewer. Not very readable, is it? The executable file contains nothing but machine code. It’s just a bunch of numbers. Side note — everything on your computer is just a bunch of numbers, whether it’s a video or a song or a photograph, but those other types of files are formatted in a way that humans can actually understand. In contrast, executable files are formatted in a way that your computer can understand. To your computer, that garbage that you see above makes perfect sense. It’s a list of instructions. Each little bit of gobbledygook you see there is a command to do something. One of them might say something like “add these two numbers together” or “ask the mouse driver where the mouse is pointing right now,” and so on.
As useful as all that might be to your computer, humans (at least the ones that I know) aren’t very good at reading that kind of code directly. OK, another side note, I did once work with a guy named Allen Hansen who could actually look at machine code and tell you what it was doing. At one point, there were a handful of instructions (those individual commands) that I could read without having to look them up in a manual. But, unless you are Allen Hansen, you probably need to work with something a little bit more human-readable! In the early days of programming, though, there was no such thing. Guys who wrote computer programs punched them in as holes in paper cards, where combinations of holes represented the different instructions. So your program, in those days, looked like this:
Don’t drop it!
Thankfully, since then, we’ve come up with much smarter ways to write programs. These days, some languages are so “high level” that they almost look just like plain old text. This is an example taken from what’s known as a “very-high-level” language, or VHLL:
% This code works in both YAP and SWI-Prolog using the environment-supplied % CLPFD constraint solver library. It may require minor modifications to work % in other Prolog environments or using other constraint solvers. :- use_module(library(clpfd)). sendmore(Digits) :- Digits = [S,E,N,D,M,O,R,Y], % Create variables Digits ins 0..9, % Associate domains to variables S #\= 0, % Constraint: S must be different from 0 M #\= 0, all_different(Digits), % all the elements must take different values 1000*S + 100*E + 10*N + D % Other constraints + 1000*M + 100*O + 10*R + E #= 10000*M + 1000*O + 100*N + 10*E + Y, label(Digits). % Start the search
Give or take a few symbols, that whole thing is pretty readable, even by someone who’s not a programmer. It’s not immediately clear what it’s actually supposed to do, maybe, but you can at least read most of it. Now, every programming language that exists today lies somewhere on the scale between these two examples. At one end is the low level and at the other end is the high level. “Low-level” essentially means that the computer can read it directly, while “high-level” means that it’s easier for a human to read. Some day in the future, the highest level language will be plain old spoken English or French or Russian or whatever you like. You’ll say to the computer, “Calculate a list of the first thousand prime numbers,” and it will churn away on your request for a fraction of a second and respond with the list. We’re getting very close to that, already. For example, you can ask Google questions in plain text and sometimes get useful responses.
So, knowing that there is a huge spectrum of languages available, why not just always choose the most high-level language available? Why not take full advantage of all the cool new features that these languages have? There are always plenty of ongoing debates on that subject. The short answer is “performance.” For every layer of cool new features, there is a price to be paid in how long it takes for the computer to do what you want. However — and this is very important — this trade-off between convenience and performance is not black and white. It’s not as simple as saying “C is faster than C++ because C++ has more high-level features.” At some things, yes, C is faster than C++. But even the notion of “faster” can be complex.
Let’s say that it takes you an hour to write a particular program in Java. That program’s job is to sort all the contacts in your phone alphabetically. When you run the program, it takes two seconds to rip through your address book and spit out the sorted list. Later, you decide that two seconds is just too long to wait. You find that you’re running this little program dozens of times every day and you just really need it to be faster. And, your address book keeps getting bigger, so that two seconds turns into three, then four, and so on. Who has four full seconds out of their day to wait for a sorted address book, these days, really!?
So, round two, you decide to rewrite the program in C. Hopefully, C will get the job done faster than Java. Well, that may be the case, but C is a low-level language (relatively speaking). It doesn’t have many of the fancy bells and whistles of Java. So, that program that took you an hour to write in Java might take you three hours in C. That obviously depends on how skilled you are at writing each respective language. But assuming that you’re equally skilled in both, it’s (probably) simply going to take more lines of code to get the job done in C than it would in Java. But the end result might be noticeably faster than the one you wrote in Java. It might sort your address book in one second!
How can you decide if it’s worth it? That all depends on your end goal. If you’re making a game, the truth is that the people playing your game almost always care about how fast your game runs. These are gamers and they will spend a thousand dollars on the latest video cards to make a game run as fast as possible. You can’t really tell these guys, “Hey, I wrote this game in LISP because I thought it would be an interesting language to play around with,” and expect them to be OK with the fact that your game runs at one frame per second.
My personal advice, for someone wanting to be a really solid programmer, is to learn the low level first. That is contradictory to the way that most schools teach. They introduce you to programming with something very high-level, like BASIC. Then, in later semesters, you might learn some more under-the-hood type stuff. I guess there’s no harm in that approach, but it makes a lot more sense to me to say “First learn what’s really going on inside the computer, then learn these high-level abstractions that we’ve created to make things easier.” For one thing, you will gain a really solid appreciation for just how much easier those high-level languages are! Spend a month learning assembly language and you’ll be jumping for joy when you get to write your first line of C code. Later, you’ll be even more grateful when you move from C to something like C++ or C#. Best of all, by the time you get to something like C#, you’ll already know what’s happening behind the scenes because you know how assembly and C work, which means you’ll pick up that C# very quickly. If you don’t know that low-level stuff, you’re going to run into a mountain of concepts in C# like “references” and “pointers” and “garbage collection” and you’re going to ask, “What are those things and why do they work the way they do?” The answer is going to be: “Don’t worry about that yet. You’ll get to those things, eventually.” Personally, I hate that answer and I don’t learn things as well if I have to just take big chunks of it on faith.
Another super-important point here is that the differences in performance between languages are very hard to measure. Programmer geeks argue about whether the comparisons are even valid. Maybe the C program is faster just because the particular task being tested happens to be better suited to C. And the biggest factor in a program’s performance is always simply the skill with which it’s written. The performance improvement gained by using C over Java might be 5% on a good day, but the performance difference between a program written by a guy who knows what he’s doing versus someone just randomly hacking out a quick solution with no understanding of the actual algorithm will be night and day. Learn lots of languages and learn how to be a good programmer regardless of the language! But know that there is, generally, a trade-off between how easy it is to program in a particular language versus how fast your program will run. Whether or not that difference matters is up to you.