Clojure is our secret sauce to quickly delivering software that solve complex problem. Clojure has some very specific advantages described in this blog post.
As Clojure daily users we deliver software systems for our clients in the toughest conditions: complex domain and demanding requirements, short delays, large organization, etc. (for example, read Michelin's feedback) We would like to share the reasons behind our choice of Clojure as our main programming language, discussing the key benefits of Clojure and why it became the cornerstone of our software studio without falling into a fanboy discourse, highlighting several years of experience running Clojure-based software in production.For an introduction to Clojure you can read the Clojure's Rationale that greatly explains its characteristics, then ACM article "history of Clojure" tells Rich Hickey's background and motivations in building such a language and goes into deeper details about Rich's language designer journey.
Clojure is powerful...combined with Software Engineering and Design Skills
Clojure is just a tool - a very powerful one - that gives developers a lot of freedom, but in a team with dozen of developers you need strong engineering discipline otherwise your code can mix different style and become a mess. Clojure is "only" a programming language, you need engineering practices and features for getting a production-ready software in the end:Observability
Performance
Reliability
Security
Etc..
The required discipline is not the subject of this post but think about architecture style (Hexagonal architecture for the better) and the various concerns of a production-ready software (log, configuration, state management, metrics, API, data schema, persistence, security, etc.). That's why we maintain our own clojure tech radar and practices that we'll publish in the future. We'll dive into the language features that gives us this power at the very end of our post. We want to focus first on the benefits outside of the programming language features.We also practice Domain-Driven Design before writing any code to explore and understand the domain (Event Storming is a great style of workshop to run in the first place), write some scenarios in Gherkin to ensure the expected behavior is shared then start structuring the domain and code it. During the exploration time needed by design we do a lot of prototyping and that's when Clojure kicks in: its interactive and exploration capabilities combined with strong language features makes it the perfect tool for our craft.Clojure is Data Oriented
It is better to have 100 functions operate on 1 data structure than 10 functions on 10 data structuresAlan J. Perlis, developer Algol language, 1st recipient of Turing Award
Few data structures / lots of functions. Data structures are deliberately few (Map, Set, Vector or List), functions are easily reused on the common data abstractions.
Clojure is THE language for manipulating data.
Homoiconicity: Code is Data, Data is code. Easy domain-specific language and code can be manipulated as data.
Persistent Data Structure & Immutability: no state sharing, easier to reason about.
Rich Data Literals:
enable interactive development and efficient feedback loop
Developer can SEE and manipulate their data
EDN: powerful serialisation/deserialisation with built-in or specific data readers
Clojure is Functional
Function are first-class citizen. Focus on composition of functions operating on immutable data arguments
Lots of useful functions in clojure.core
Pure functions (idempotent and without side-effect) increase reusability and allow to reason « locally » just considering the function at-hand (not considering prior or global state and all possible inputs)
Everything is an expression, easier to reason about, almost no statements nor complicated state management
Declarative approach and data literals
Clojure's data literals are rich and powerful. A data literal is a notation for representing data structure declaratively and directly in the programming language source code, instead of imperative invocations to construct the data structure such as in Java in the following code block:Map m = new HashMap();
m.put("key1", "value1")
...//etc.
Data literals in Clojure works for scalars (Integer, String, etc.) and composite (Sequential and Hashed collections). The main benefits is readability and very low cognitive load to understand the shape of data through an example and moreover to translate the domain concepts directly in data structure. The bowling game example perfectly fits this objective:Here we can directly translate this sample to a data structure and develop the language of our domain, a frame is a tuple, a vector with two integer elements and game is ten tuples, each game is then associated to the player.(def game1 {:title "MCBA ALL - TIME HIGH TEAM GAME & SERIES 794 855 705=2354"
:location "STRATHMORE LANES"
:date #inst "2003-09-25"
:games [{"Jack Jackson" [[:X ] [:X] [:X] [:X] [:X] [:X] [:X] [:X] [9 :/] [9 :/ :X]]
"Scott Reid" [[9 :/] [:X] [:X] [:X] [:X] [:X] [:X] [:X] [:X ] [:X :X 7]]
"Joe Krajkovich" [[:X ] [:X] [:X] [:X] [:X] [:X] [:X] [:X] [:X ] [:X :X :X]]}]})
The data structure above can be read and understood directly with the lowest cognitive load possible, almost like the bowling game sheet showed previously.A score
function takes a game as argument and returns the total and intermediate scores like this:(score [[:X ] [:X] [:X] [:X] [:X] [:X] [:X] [:X] [9 :/] [9 :/ :X]])
;=> [[30 60 90 120 150 180 209 229 248 268] 268]
The data literals are also extensible with tagged elements and readers that converts a String representation to a in-memory structure...and that's perfect for DDD's Value Object (I prefer calling them Value Type because the goal is to construct a value, and the type is just the description of the structure and behavior associated to a value). Exemple: the code #inst "2018-03-28T10:48:00.000"
doesn't return a String but an Instant java Object.We can express a domain example directly and start coding some behavior around sample data structures, and that leads to the next section, the data literals richness in Clojure makes the interactive programming experience really shines as the developer is then able to "see the data" directly in the source code and REPL (and not thanks to a debugger, a println
or log output, a database visualizer, etc.).Domain Exploration through Interactive Programming Experience
The Clojure developer experience is strongly linked to the editor and the famous REPL. This interactive environment puts us in a Flow state or "in the Zone" and there lies a lot of the productivity that come with Clojure. In short, the REPL and the associated editor allows very quick feedback loop. There is the REPL but the whole language contribute to a developer experience that's like no other: immutability, short pure function and moreover rich data literals. That experience enables tremendous developer productivity but also a lot of fun and joy!The interactivity of the developer experience impacts not only the "code construction" activity but also the debugging and code exploration activity during maintenance. Thanks to Clojure's pure function whose use is facilitated by the language, just invoking the function with some data is enough to debug. For complicated case we can even connect a REPL to a running system remotely and debug it (even if the system to debug is 150 million miles away).Clojure's dynamic nature and interactive development environment allow for rapid development cycles, quick code iteration, and a REPL-driven development approach. This enables our team to quickly build, test, and refine solutions, leading to faster delivery of high-quality software. Additionally, the ability to redefine functions on the fly makes debugging and code exploration a breeze.Low developer cognitive load
There are several characteristics of Clojure that made the developer's cognitive load very low, hence increasing productivity by focusing on the domain at-hand only:Pragmatic Functional Programming: the core of FP is to compose functions that transform data in a kind of pipeline. Functional programming emphasizes immutability, purity of functions, and the use of higher-order functions to manipulate data. This approach reduces side effects and promotes a good separation of concerns.
Pure-function and almost no global-state or complex state management: this allows "local-reasoning" as function works only on given arguments (Local reasoning is a property of some code wherein the correctness of the code can be inferred locally under specified assumptions, without considering prior application state or all possible inputs). Code reuse is a unique consequence of that characteristic, I often reuse code in some open-source libraries by just copy-pasting the function I'm interested in and just follow the dependencies in it, very often the functions in dependencies are very few and I can borrow some code very easily, a thing I seldom encounter in other languages.
No statements only expressions: every expression returns something (even side-effect one like println
that returns nil), this force code to return something and to avoid state change, this makes it easy to evaluate and understand any piece of code given some data.
Very concise code: in the end, the verbosity of the code increases the developer's cognitive load, Clojure's conciseness makes it very easy to glance at a piece of functionality all at once. Clojure eliminates most template/boilerplate code: nearly all code that we write is directly related to a functionality.
In summary, the Developer Experience with Clojure is exceptional, with the three pilars of what constitutes the Developer Experience: Feedback loop, Flow state and low cgnitive load. The article about what actually drives developer's productivity by Nicole Forsgren et al. of the "Accelerate" fame explains it clearly.Clojure is hosted on the JVM and JS engines
Clojure is hosted firstly on the Java Virtual Machine, which means it inherits the performance, stability, and ecosystem benefits of the JVM. Clojure also runs on various JS engines with the Clojurescript compiler that emits Javascript code from Clojure. This allows us to leverage existing Java and Javascript libraries and tools (existing JDBC driver, React, etc.), ensuring we don't have to reinvent the wheel when it comes to functionality. Java code can be seamlessly integrated given any particular needs with clojure's Java Interop feature. Additionally, the JVM's robust garbage collection and multi-threading capabilities ensure respectively easy and secure memory management as well as performant and efficient code.Having different host also means we can write our domain logic in pure Clojure and being able to execute it either on the client (JS) or server-side (JVM). Nowadays a lot of Clojure libraries comes in pure Clojure version that runs on both Javascript and Java. Then our developement team only needs to master one language and the separation between frontend and backend development, that doesn't to us very natural at first, is then completely unnecessary.Ecosystem, Community and recruitment
The Clojure community is active, supportive, and welcoming, with a wealth of resources, libraries, and tools available as well as places to interact with others (Clojurians Slack, Clojure Website guides, cljdoc, Community-powered documentation and examples with ClojureDocs, and also about 40k repositories on github.com ). I found the signal/noise ratio of the community excellent with a focus on getting work done wisely and efficiently. The community is mainly composed of senior individual and/or ones passionated about pragmatic software engineering.One of the usual complaints about Clojure is the ability to find developers easily. We observe the opposite: we succeed to find people with great software engineering skills, often coming from the Java ecosystem and tired of the complexity of big framework and attracted by the pragmatism and efficiency of the language. For junior, the learning curve is very quick for the language but we found the same concerns than for any other junior engineer: learn the craft and enginering of building good software systems and it takes time and experience to do so, no silver bullet.The Clojure community emphasises the importance of stability and backwards compatibility, this is a seldom value that I don't find to such level in other programming communities (looking at you Javascript!). It's a reminder everytime we upgrade a Clojure's version to observe old code that still works seamlessly. Also the community praise old and battle-tested libraries that is not often updated (even if at one time many libraries had 0.x versions which gave a feeling of incompleteness even though they were production-ready, the Core team undertook a communication effort to move the mature libraries to version 1.0).Clojure in Production
Building a system is easy but running it is the real test and Clojure also shines in this area:Being hosted on the JVM, the tools are the same than an Ops would use for any other traditional Java system: we use Docker and Kubernetes hosted with the usual Cloud providers, JMX, Prometheus, etc.
The performance tools are the same: you can use VisualVM for broad insights, profile your application with a flamegraph dedicated tool based on the JVM async-profiler lib.
The troubleshooting activity is somewhat special with Clojure: the functional nature of the code makes it very easy to reproduce an invocation context and troubleshoot a problematic piece of code, moreover we are also able to connect to a running system through a REPL and explore any part of it.
Other powerful features but of less importance
Concurrency
When you have parallel and concurrent processing to do and don't want to worry about the common pitfalls like race conditions or deadlocks, Clojure provides software transactional memory and moreover the amazing core.async library which is based on the Communicating Sequential Processes, or CSP, a language for describing patterns of interaction in concurrent systems. The Go language concurrency feature has the same foundation. Everytime I use core.async I'm amazed at the easiness of building concurrent features without headache and bugs. The chapters of "Clojure for the Brave and True" has excellent coverage of these topics: concurrent and parallel programming, Clojures's STM, concurrent processes with core.async,Macros
Clojure's macro capability is a powerful metaprogramming tool, that's to say it helps to write code that write code. With macros we are able to take any data structure, unevaluated, and transforms it the way we want to produce valid Clojure code. This capability is often used to produce repetitive code from a more concise version specific to the task at-hand (then called Domain-Specific Language). This ability to manipulate code as data, and vice versa, stems from the Lisp heritage of Clojure and is known as "homoiconicity" (a perfect word to shine in society!).Why Building and Running Software with Clojure is cheaper?
- No waste during development
- Full efficiency of the developers, focus on the problem at-hand, free the developer potentiel to focus on its problem
- Strong feedback loop and interactive development, developer always in a flow state
- Very low cognitive load, always local reasoning
- Prototyping is the fastest way to move forward in software development
- Clojure community has a strong focus on software design discipline and the software engineering level of the people we meet in the community is very high.
Clojure allows easy reproductibility of the execution context when dealing with support case. Clojure makes it easy to reproduce a case’s execution context with light setup. No ceremonies, like in Java, to reproduce a support case.
For the toughest support case, we can even connect to a running system and inspect it interactively.
Conclusion
By choosing Clojure as the foundation for our software studio, we have embraced a powerful, elegant, and expressive programming language that allows us to efficiently build high-quality, maintainable, and scalable applications. The Clojure community emphasizes using libraries instead of frameworks and Clojure's code eliminates most template/boilerplate code: nearly all code is directly related to functionality. Clojure is a boring technology but very fun and joyful to use, it releases our full potential for solving problems, in our opinion having strong software engineering skills + using Clojure is a killer combination.The benefits of functional programming, the JVM ecosystem, and Clojure's rich data structures and concurrency model have proven invaluable in our projects. Combined with a thriving community, we are confident that our bet on Clojure will continue to pay dividends for our studio and the solutions we provide to our clients.Clojure is not "magic", fashionable or revolutionary, but it has a lot of small improvements and niceties which, taken together, result in a huge difference when writing real-world large and complex systems."The greatest single programming language ever designed
Lisp is worth learning for the profound enlightenment experience you will have when you finally get it; that experience will make you a better programmer for the rest of your days, even if you never actually use Lisp itself alot."
One of the most important and fascinating of all computer languages is Lisp (standing for "List Processing"), which was invented by John McCarthy around the time Algol was invented.Douglas Hofstadter, Gödel,Escher,Bach
Within a couple weeks of learning Lisp I found programming in any other language unbearably constraining.Paul Graham, Road to Lisp
Lisp is the most sophisticated programming language I know. It is literally decades ahead of the competition...it is not possible (as far as I know) to actually use Lisp seriously before reaching the point of no return.Christian Lynbech, Road to Lisp
Other things to read about the choice of Clojure