Learning foreign languages helps to broaden your mind because some concepts are much better expressed in one language than another. German, for example, really has no word for "fluffy," although it does have words for "furry" and "fuzzy." Similarly, English has no elegant way of expressing the difference between libre and gratis in Spanish. Natural languages adopt concepts from each other over the years and tend toward the same expressive abilities.
Programming languages are different. A programming language may be defined by a specification, but is always limited by the abilities of the compilers and interpreters that execute it. If a particular language doesn’t support a given feature, you can’t just steal a few words from another language and use them, as you might with a natural language. While you can combine languages in a single project, you typically have to do this across well-defined interfaces, rather than by just stealing some syntax from one language and using it in another.
This kind of thing isn’t just an abstract problem. If you’re writing code that queries a database, you’re probably going to be using at least two languages. Because most general-purpose languages don’t have semantics that map clearly onto a database, you’ll need to embed something like SQL or XPath. Now, imagine that you want to express a transitive closure in your query. SQL isn’t expressive enough to implement a transitive closure (although some vendor-specific extensions permit this), so you might have to implement it by using a stored procedure in a third language.
The ability to use different languages when they suit the task at hand is a sign of a good coder. The more languages you learn, the easier it is to pick up a new one. Eventually, you start thinking of every new language as just a set of modifications to a language you know already. So what languages should you learn that will help you to quickly build up the set of basic concepts and let you pick up other languages easily? The rest of this article contains my answer to this question. Note that I’m not necessarily advocating using any of these languages for a real project, but I believe that learning them will make you a better programmer in whatever language you do use.
C: Portable Assembly
Whatever language you choose for writing code, eventually that code will be turned into a set of very simple machine instructions. These instructions will be executed more or less sequentially (superscalar architectures notwithstanding). A good programmer needs to be able to think at multiple levels of abstraction simultaneously, and the best way of accomplishing this goal for the lower levels is to use a language that closely mimics processor operation.
A decade or two ago, the most sensible way of achieving this objective would be by learning assembly language. These days, however, few processors actually execute machine language instructions directly, so a language like C isn’t much different in terms of abstractions. A modern x86 CPU, for example, typically has some hidden registers used for storing items on the stack, so the items that an assembly language programmer believes are stored in registers are not quite the same as those that the CPU actually stores.
Being one layer of abstraction higher than an assembly language, C has the advantage that it’s not tied to a particular CPU architecture. This faculty has helped C to gain widespread use; even if you never write a line of C code, you undoubtedly will have to read C code at some point or other.
On VMS, there was a well-defined ABI for all procedural languages, so it was trivial to call one from another. Microsoft Windows had COM, which allowed all languages to deal easily with objects from other languages (as long as both languages were C++), and now we have .NET, which does the same thing (as long as both languages are C#). In the UNIX world, the standard way of exporting an interface to a library is via C, irrespective of which language you’re using. For a program in language A, calling a function in a library written in language B almost always involves going via C.