What makes Clojure REPL great
I've been learning Clojure lately. It's my first experience with a Lisp dialect and I'm enjoying it. So far my favorite surprise is REPL! I've heard the promise of “interactive coding” or “live updates” before from other languages, but this is the first time such a promise has actually payed off for me. REPL has had a real impact on my, fairly limited, Clojure programming experience. In this post I'll explore why it works so well for me, while some seemingly similar technologies haven's had nearly as much of an impact.
How others fail
I'll start by exploring languages I've used in the past and how their interactive shells have failed to be as transformative as Clojure's REPL.
- Web apps these days are far removed from basic console interactions. We use build tools such as Webpack to bundle and transpile our code, and frameworks like React to render the app. Not to mention using language features not yet supported by the browser, or coding in TypeScript.
- Web apps are heavily intertwined with DOM, or some equivalent internal state. This makes poking at my code from browser console more difficult as it is often tied to DOM events or depends on a complex state.
Back with Java 9 in 2017 Oracle introduced JShell:
The Java Shell tool (JShell) is an interactive tool for learning the Java programming language and prototyping Java code. JShell is a Read-Evaluate-Print Loop (REPL) ...
Is it cool that Java has an official REPL? Yeah, I guess. Has it in any way changed my approach to writing Java code? No. It's notable that I felt the need to link to the docs, as if to prove this thing actually exists. I use Java daily and most of the time I simply forget about JShell.
The greatest impediment to good REPL experience in Java is its object orientation along with all design patterns used with that paradigm. For example, I rely on Spring's dependency injection to bootstrap my Java apps. In unit tests I need Mockito library to help me mock all dependencies of a class before I can start working with it. This makes it near impossible to interact with my code from REPL. Imperative code structure and verbosity don't help either.
Other languages I've used in the past usually have some combination of these flaws. For example Go has a fast compiler. In fact people go as far as using it as “scripting language” in place of shell scripting. However, the speed of compiler itself is not enough. Being highly imperative Go would make for an unpractical REPL experience.
How Clojure succeeds
Previous examples illustrate a few key points. For a good experience it's not enough that a REPL, or a similar interactive shell, simply exists. It's also not a necessity for the language to be interpreted (Clojure is not, mind you). It might be a good idea to have a relatively fast compiler, but that in itself is not enough.
So, why is Clojure REPL so transformative? Here are a few language characteristics that I found critical to my usage:
- Expression based syntax
- Functional paradigm
- Dynamically typed
- Simple at scale
Expression based syntax
This is where imperative languages fall short. In Clojure the code is structured as a series of expressions. This lends itself extremely well to REPL interaction, which is essentially a loop of reading and evaluating expressions. My “normal” Clojure code, which I store in files, doesn't differ too much from a series of expressions I input into a REPL session. This makes interacting with REPL feel as natural as coding. There's no urge so quit REPL and just dump it all in a script file.
Being functional helps the language be expression based. Beyond that it favors composition. In Clojure complex behaviors are implemented by combining simpler functions. This is in contrast with Object Oriented languages where primary tool is inheritance. With OO complex features are implemented by building type hierarchies, which breaks the REPL experience. If I want to poke around at different segments of a composed function I invoke its component functions. With type hierarchy on the other hand I'd probably need to implement abstract classes to try to isolate and invoke some of their methods.
In addition to composition, another benefit is working with mostly pure functions. The fact that things I'm playing with in REPL don't have side-effects is hugely helpful. REPL shines in exploratory interactions and being able to repeat function calls without messing up some internal state is critical.
One benefit of dynamic typing is capability of functions to care less about their parameters. As long as parameters contain particular data that function needs it doesn't matter what else is in there. In Clojure there might be some state carried over between functions, but they themselves don't care about entirety of it. All that's needed to invoke a function in REPL is it's own minimal set of data.
Of course, extra benefit is not having to write a class for every single piece of data.
Simple at scale
In some of the other languages REPL starts promising, but there's a breaking point which production applications cross and it becomes useless. Think of Java hello world example vs. a minimum Spring Boot app. Amount of code might not be all that different, but effectiveness of JShell tanks.
I lack personal experience to speak on this point. As I mentioned I'm just starting with Clojure, and I haven't worked on any production Clojure apps. However, simplicity is famously one of the core objectives of Clojure language. There's plenty of testimony from experience Clojurians to attest to this. So I'm optimistic about my REPL being able to keep up with me as my programs grow in scale.
In the end it took a particular language built around a set of conscious design decisions to ignite my love for REPL. In hindsight it seems obvious that design of a language would influence programming techniques that work well with it. However, that gets increasingly overlooked in current programming landscape where more and more languages strive for feature parity.
If you'd like to get in touch with me, either to share in my enthusiasm for Clojure, or to tell me that I should have learned Python and saved myself all this overthinking, you can find me on Mastodon @antolius.