Learn New Languages!
I have long been a firm believer in learning new languages. I believe that the languages we speak strongly influence how we think, and what we are able to think about. Similarly, our programming languages are the tools by which we formalize our ideas as executable code, and so they influence our thinking about how to solve problems. Our thoughts are constrained by their concepts, by their fundamental building blocks.
I am not alone in believing this. The linguist Benjamin Whorf (1897–1941) is often quoted stating that “language shapes the way we think and determines what we can think about”. This is an old sentiment, and one supported by some very interesting empirical results in newer research as well. And this is, of course, exactly what makes learning new languages so very valuable; It does not only expand your vocabulary, but will actually expand your mental toolbox.
Alan Perlis, in one of his his Epigrams on Programming, famously stated:
“A language that doesn’t affect the way you think about programming, is not worth knowing.”
While this is a somewhat pointed remark — there might sometimes be good reasons to get into a language similar to one you already know — I nonetheless consider it good advice. Seek out the strange and unfamiliar languages! It is only by learning new paradigms, or languages with concepts fundamentally different from those you know, that you truly change the way you think. These languages are of course also the hardest to learn. But once it clicks, the epiphany will be well worth the struggle to get there.
For me, some of these epiphanies have been things like the following:
- Making robust programs by letting them fail. Erlang was designed at Ericsson in the 80s for creating robust and distributed systems. They solved this by embracing failures, not by trying to avoid them at all cost. Making self-healing systems that will re-spawn processes that go down, while making sure that the system as a whole keeps working, turned out to be a lot more powerful than simply trying to avoid failing. And it’s an idea that can easily be applied to other languages as well.
- Programming by constraints. Sometimes it is a lot simpler to describe a problem, along with its constraints, rather than implementing a solution to the problem yourself. This is exactly how you do things with some declarative programming languages. The best known example of a language that works this way is Prolog, but some languages gives you the same abilities through libraries (e.g. MiniKanren in Scheme, or core.logic from Clojure).
- Programming without mutations. In most functional languages you strive to avoid mutation, using purely immutable data structures and variables. For me, realizing that mutable state isn’t required to get things done really changed the way I think about programming. The degree to which this is enforced by the language varies among different functional languages, but good examples include Haskell, OCaml, F#, and various Lisps.
- Higher order programming. Another epiphany you get by learning a functional language is that functions are no different from any other data type you work with. When functions are first class citizens you can write functions using other functions as input parameters or return values (thus creating higher-order functions). This adds a whole new layer of possibilities of how to express your programs.
- Programming with patterns. Programming centered around pattern matching can be extremely powerful. It takes on different forms in different languages, but the idea is to decide what to do depending on the structure of the inputs. Some languages, like Awk, are entirely centered around this. Each line of input is matched against the patterns of the program, and for each matching line some statement is executed. In other languages, usually functional ones, pattern matching is used as one of many control structures, and matching is typically done on the type and/or the value of some variable. Haskell and F# are examples of languages with good pattern matching mechanisms.
- Homoiconicity. This is a word you hear thrown around in Lisp circles. It basically just means that the language expresses code and data using the same syntax. This is what empowers the great macro systems found throughout languages in the Lisp family. Being able to treat program code like any other data structure makes it easy to make changes to the language itself!
- Lazy evaluation. Most programming languages use strict evaluation rules. You better not create infinite recursion, or your program will blow up! Introducing lazy evaluation turns some of the ways you have to think about composing programs upside down. Suddenly it’s perfectly fine to implement an infinite list of elements, as long as the code consuming it returns at some point, or at least doesn’t try to use them all at once.
These are some of the concepts I have enjoyed learning about from various languages. I could come up with more examples, but my point is this: There are so many great ideas out there. Go learn something completely outside your usual comfort zone, even if you won’t be able to use it in your day-to-day work. You might just end up being a better programmer for it.
So, when did you last teach yourself a new language? Which language will you look into next?
Cover photo by Shannon Kokoska