My previous blog, Deliberate Practice, discussed the need for developers to “sharpen their pencil” continually, by setting aside time to learn how to tackle problems in different ways.
However, the Sapir-Whorf hypothesis, a contested and somewhat-controversial concept from language theory, seems to hold reasonably true when applied to programming languages. It states that:
“The structure of a language affects the ways in which its speakers conceptualize their world.”
If you’re constrained by a single programming language, the one that dominates your day job, then you only have the tools of that language at your disposal to think about and solve a problem. For example, if you’ve only ever worked with Java, you would never think of passing a function to a method.
A good developer needs to learn many languages. You may never deploy them in production, you may never ship code with them, but by learning a new language, you’ll have new ideas that will transfer to your current “day-job” language.
With the abundant choices in programming languages, how does one choose which to learn? Alan Perlis sums it up best.
“A language that doesn‘t affect the way you think about programming is not worth knowing“
With that in mind, here’s a selection of languages that I think are worth learning and that have certainly changed the way I think about tackling programming problems.
Clojure
Clojure is a Lisp-based language running on the Java Virtual Machine. The unique property of Lisp is homoiconicity, which means that a Lisp program is a Lisp data structure, and vice-versa. Since we can treat Lisp programs as Lisp data structures, we can write our code generation in the same style as our code. This gives Lisp a uniquely powerful macro system, and makes it ideal for implementing domain specific languages. Clojure also makes software transactional memory a first-class citizen, giving us a new approach to concurrency and dealing with the problems of shared state.
Haskell
Haskell is a strongly typed, functional programming language. Haskell’s type system is far richer than C# or Java, and allows us to push more of our application logic to compile-time safety. If it compiles, it usually works! Haskell is also a lazy language – we can work with infinite data structures. For example, in a board game we can generate the complete game tree, even if there are billions of possibilities, because the values are computed only as they are needed.
Erlang
Erlang is a functional language with a strong emphasis on reliability. Erlang’s approach to concurrency uses message passing instead of shared variables, with strong support from both the language itself and the virtual machine. Processes are extremely lightweight, and garbage collection doesn’t require all processes to be paused at the same time, making it feasible for a single program to use millions of processes at once, all without the mental overhead of managing shared state.
The Benefits of Multilingualism
By studying new languages, even if you won’t ever get the chance to use them in production, you will find yourself open to new ideas and ways of coding in your main language. For example, studying Haskell has taught me that you can do so much more with types and has changed my programming style in C#. A type represents some state a program should have, and a type should not be able to represent an invalid state. I often find myself refactoring methods like this…
void SomeMethod(bool doThis, bool doThat) {
if (!(doThis ^ doThat))
throw new ArgumentException(“At least one arg should be true”);
if (doThis) DoThis();
if (doThat) DoThat();
}
…into a type-based solution, like this:
enum Action { DoThis, DoThat, Both };
void SomeMethod(Action action) {
if (action == Action.DoThis || action == Action.Both)
DoThis();
if (action == Action.DoThat || action == Action.Both)
DoThat();
}
At this point, I’ve removed the runtime exception in favor of a compile-time check.
This is a trivial example, but is just one of many ideas that I’ve taken from one language and implemented in another.