Thursday, June 23, 2022

Distinguishing features of Clojure

By now it should be readily apparent that Clojure is not like most programming languages. In this post I will attempt to pin down what I think makes Clojure so different, focusing more on the philosophical aspects rather then the implementation details. Clojure is distinguished from most other languages from how tightly it is integrated into the JVM, and by its excellent Java interop - but this is an implementation detail and not the philosophical core of the language.

Clojure is sometimes called a functional language, but it is not too particular about it. Functional programming is just one paradigm under the larger multi-paradigm umbrella. The emphasis on functional programming in particular gets to something deeper about the language, which is its emphasis on concurrency. In Clojure you can have side effects, but they ought to be concurrent.

Code as data

Clojure is a Lisp which means it treats code as data. In the customary manner of Lisp dialects, code is represented by collection data structures. Consequently, like most Lisp dialects the language is optimized around the manipulation of collection data structures like lists and sets. This combination of an emphasis on collection processing and code as data enables the Clojure macro system.

It should be mentioned that code as data is a philosophy rather then a specification. Like so many things it is a work in progress. Clojure did well to extend the simple S-expressions of earlier Lisp dialects with new syntax to create the extensible data notation. As a work in progress, you could further extend the extensible data notation, to add improvements to it. Future language designers may then improve it further.

Separating identity and values

The second most important aspect of Clojure is its emphasis on concurrency and immutability. Clojure has a state model that separates values from identities. Values are immutable but identities are associated with a series of values over time. Clojure doesn't prevent side effects, but it does say that you ought to log them to model changes over time. This enables STM concurrency.

Most of Clojure's constructs: its persistent data structures, its very strong emphasis on immutability and explicit modeling of state, are explained by the overarching goal of supporting concurrency. Immutability aids concurrency because immutable values can be safely shared between threads. Clojure doesn't eschew side effects, but it does emphasize that they ought to be concurrent.

Clojure certainly wouldn't appear in the shape it does now if it was created for any other purpose then its support for concurrency. If for example Clojure was designed with functional programming as an end in itself, it might have been a purely functional language. Instead, it was designed with concurrency in mind, so it doesn't appear like that. Functional programming is a means to an end. It is the currently acceptable means of implementing Clojure's goals of concurrency and explicit progression of time constructs.

Clojure's role in history is that it is a concurrent homoiconic language. There is nothing else in existence like it. It represents a truly unique programming philosophy. I think any other property of the language could be changed if it respected this core philosophy. The properties of being dynamic, functional, hosted, etc are more like implementation details then distinguishing characteristics of the language.

After all, Clojure is nothing like almost any dynamic language you have heard of. Dynamic languages almost never model state like Clojure does. Besides, Clojure developers make frequent use of type hints so that it can often appear like a statically typed language anyways. The typing discipline, the use of type hints, etc is just an implementation detail like any other. Hosting on the JVM is the logical thing to do when implementing a programming language.

These are all implementation details. Even the use of functional programming is mainly because it is more convenient when dealing with state management and concurrency, but as always functional programming must be part of a larger multi-paradigm umbrella. So functional programming is just another implementation detail. The main point is the code is data philosophy and the explicit modeling of time.

No comments:

Post a Comment