Languages and Axioms
I learned Common Lisp last summer. I had been using some combination of Python, C++, C, and Java for about 8 years, and I wanted to break out of my popular language box. I was more or less attracted to the “data as code” idea, but really it had a bunch of selling points that I would have appreciated:
- Its data was stored in the same structures as its program.
- It had macros that were powerful and simple. That’s huge coming from C++.
- It was REPL-based, something I never took advantage of with Python.
- You could create new code on-the-fly at runtime from lists.
- Completely dynamic and backed by garbage collection.
- The language itself is very mutable.
The sum of its parts was greater than anything that I had ever used before, and it quickly became my favorite language. I learned quite a bit about functional programming techniques, did some experimentation with image processing and database application programming, and worked through all of Graham’s ANSI Common Lisp and large swaths of Norvig’s Paradigms of Artificial Intelligence Programming.
Recently, a nagging though started to cross my mind: What if I didn’t go far enough to break out of my box?
That didn’t happen until two weeks ago, when I started porting Genesis, my toy genetic algorithm framework, to Clojure for fun. I wasn’t very aware of the features of Clojure: I knew that it was a Lisp on the JVM and that it supported Software Transactional Memory, but I didn’t think it would be that different from Common Lisp aside from Java interoperability.
To clarify, I expected that the primary method of manipulating Clojure would be list-based, and that it would still mostly turn a blind eye towards mutable data and multithreading issues, with the option of phrasing memory updates in terms of transactions if you were using multithreaded applications.
Clojure turned out to be different than any language that I had ever tried – save my forays into Haskell to grok enough syntax to follow sigfpe‘s easier articles. If you trust the language and work with the core Clojure data structures, it takes a lot of the worrying about parallel programming out of your hands. Its creator, Rich Hickey, ended up with a language that has a good balance between functional programming and practicality. It is definitely a good Modern Programming Language.
You Said Something About A Box..?
Right, I said I didn’t go far enough outside of my comfort zone when learning Common Lisp. That’s really only half true.
From the perspective of programming, Common Lisp is a great language. Using the functional programming primitives provided on top of lists feels very natural, and I was able to work on complicated problems with relative ease. Even though I don’t use the macro system in Common Lisp very often, it usually provides a great fallback for either refactoring or changing the way the problem can be represented. The REPL is just icing on the cake.
However, not all is flowers and roses. Looking at Common Lisp from perspectives other than single-threaded programming, it begins to look mortal! For instance, the Common Lisp standard was written without a thought to having multiple threads of execution. Although most implementations provide extra support for multithreading, you’re back in the same locking boat that you were with any of the other popular languages once you need to share mutable data. Naturally, the syntax for things like grabbing mutexes is easier with Lisp than with other languages thanks to the “with-” design pattern for macro writing, but you’re not gaining much with respect to avoiding race conditions.
Looking at it from a data-processing perspective, functional programming is easy in Common Lisp, but it isn’t required at all. There’s nothing stopping the programmer from falling back into using local mutable state, functions with side-effects on global variables, and destructive functions. Combined with the single-thread design, that’s fine. You can’t avoid state all of the time, and I don’t go out of my way to be strictly functional. That’s how most other languages are designed. Hell, the Common Lisp standard even offers a whole slew of functions that are destructive for the sake of efficiency!
I’m only really getting at the fact that even the ultra-flexible of Common Lisp wasn’t written to handle all computing scenarios.
Some languages have support for neat automatic threading features: Haskell has extensions where type annotations can allow automatic parallelization, and you can use OpenMP in C++ for automatic parallel processing. For those wondering, Erlang is somewhere on my list of languages to take a look at, but it has to wait its turn! I’m still having too much fun with Lisps.
Axiomization and Language Design
Love him or hate him, Paul Graham has written quite a bit about the design of Arc. I’m not going to talk about it directly, but I will borrow his idea of reaxiomizing the core of a language as a means of producing better code on top of it. He mentions axioms as neither primitives nor library functions, but I will consider primitives as well.
I’ve talked about this a little in the past. For instance, I’ve made the case for operator overloading in Java. It was explicitly verboten in Java, as decreed by the designers. In fact, written in an early Java whitepaper titled “The Java Language Environment“,
2.2.7 No More Operator Overloading
There are no means provided by which programmers can overload the standard arithmetic operators. Once again, the effects of operator overloading can be just as easily achieved by declaring a class, appropriate instance variables, and appropriate methods to manipulate those variables. Eliminating operator overloading leads to great simplification of code.
By the letter of their assertions, they were correct. A lot of programmers would never need operator overloading to produce good code. In fact, if you’re using it just for the sake of expressiveness, you’re going to produce an unintuitive hodgepodge most of the time. However, a small but powerful minority – particularly those who do mathematical work for a living – would absolutely benefit from being able to write inline operators. I mean honestly, have you ever tried to use a BigInteger to implement something more complicated than RSA? The best that will happen is that it will end up… verbose.
Essentially, leaving out operator overloading forced Java to be more verbose and/or less readable than it needed to be. Leaving it in as an ‘axiom’ could measurably produce more compact, readable code for programmers who used it judiciously.
As an aside for those who think I’m just trolling Java, there is definitely a great Java feature for each one that misses the mark. For instance, the enums in Java follow the idea to its logical conclusion.
If you’ve been incensed by my Java discussion or are otherwise unsure that axiomization could lead to a better language, let’s look back at Lisp. Lisp changes quite a bit if you remove utility functions like reduce: it would break a lot of the compactness of the language and ruin some otherwise good functional programming. Now, it doesn’t support operator overloading, per se, but there’s nothing stopping you from changing the language to support it.
Where the Hell is All of This Going?
Back to Clojure! Clojure is based on a much different set of axioms than many of the popular languages. At its core, we see support for thread-safe data structures whose data are immutable and persistent. These were written in as axioms to the language, and you can make some good predictions for the language, assuming people stick with it.
The biggest one is that as libraries are converted to Clojure or abstract away their Java API, Clojure will develop a large set of libraries that could be given a thread-safe guarantee. For my day job, I work in a C++ shop that uses a bunch of C libraries, and we need to constantly be aware of whether or not our base libraries are thread-safe. My workplace recently developed a non-Clapack fork of our linear algebra abstraction for this reason: using libraries that aren’t designed to be threadsafe in a multithreaded environment builds bombs.
I’m too young to get a sense of what the next 10 years of programming languages might look like – but if you pressed me, it would look a lot like it does today, and we wouldn’t be better off for it. The languages that are currently in the design phase are slowly adding quite a bit of features while remaining mostly the same: for instance, C++ is getting a makeover that includes concepts, lambdas, futures, and a multithreading primitive. I’ve known C++ for over half of my life (holy shit!), and a lot of the features are sorely needed. However, it’ll be an incremental improvement at the cost of obscure compiler bugs and increased complexity.
These aren’t easy problems: for instance, if you didn’t care a lick about any external concerns like backwards compatibility and wanted to change C++ to support lambdas, how could you without just tacking it on? However, we have new languages that can pick up where the old languages left off, and continuing Fred Brooks’ prediction, there will be no silver bullet, only incremental improvements. Languages like Clojure are a step in the right direction as far as testing new computing ideas.
Popularity: 21% [?]

Reader Comments
That entry was particularly enjoyable.