Why We Use Clojure at Crossbeam

SHARE THIS

By Buck Ryan

- Crossbeam News

At Crossbeam, we invented the category of partner ecosystem platform in 2019. It’s a new kind of software — our service acts as a “data escrow” — which means allowing our users to upload and map large and complex data sets with their partners. It also means we needed to attract developers who were as ambitious as our product.

We knew we had to have a strong foundation from the start.

That’s why we went with Clojure.

Why Clojure

Clojure is an extremely simple, but powerful, functional programming language. Because of its simplicity, our code remains easy to understand and change, even as our code base gets bigger each year. To us, it’s no surprise Clojure was ranked the second most-loved language in the 2021 Stack Overflow Developer Survey.

The success we’ve had in hiring engineers and scaling comes down to this: writing Clojure is delightful.

For engineers who already know Clojure, they’re often looking exclusively for Clojure jobs. And for those who do not know Clojure, the excitement of working with this unique language is regularly a selling point.

We asked some members of our team how Clojure affected their decision to work here and this is what they had to say:

- “honestly learning clojure was a huge plus“

- “100% Clojure affected my decision”

- “I was only looking for jobs in clojure. so it was about 100% of my choice“

- “people who work on Clojure come from varied backgrounds (not just Computer Science) which makes them really cool 😎”

This has also resulted in Clojure being a great signal for us. People who express a desire to work with Clojure, whether experienced with the language or not, are often the same people who like challenging work, want to hone their skills, and who have a passion for engineering.

The excitement of working in Clojure doesn’t stop after getting hired. Here are a few more quotes on our team’s experience learning this language:

- “Have to say just like when I learned Vim..Clojure has made programming fun again“

- “I have enjoyed the mental shift from my regular OO approach to thinking of problems in terms of just plain data“

Watch two members of our team, Nick and Yiannis, give a talk about Clojure at Technical.ly Philly’s DevConf.

How Clojure Works

We have found Clojure to be uniquely expressive: developers struggle very little to write what they’re thinking. Instead, the lack of syntax and the language’s intuitive behaviors allow it to get out of the way and for coding to just work.

Clojure is a functional programming language, which means functions are first class citizens. Higher levels of abstraction are created by composing functions together. Like most other functional languages, Clojure embraces immutable data types, which are key for building code that is easy to understand and change over time.

Functional programming is quite important to us and we’d argue it’s critical for building systems that are easy to understand, change, and keep bugs at a minimum. Many of these concepts are captured very well in a talk by Clojure’s creator, Rich Hickey, called the Value of Values.

Lisp and Clojure

Clojure is not just functional, it’s also a Lisp. Our team loves Lisp because of the uniformity* of the code and the power that comes through macros (macros are an in-depth topic, which we won’t cover here). If you haven’t seen a Lisp language before, it can be a bit jarring – parentheses are everywhere.

Here’s a JavaScript function and the equivalent Clojure function which adds numbers together.

Javascript:

// JavaScript function add(a, b) { return a + b; }

Clojure:

;; Clojure (defn add [a b] (+ a b))

Even in such a short snippet we can see differences that make Clojure great:

- Function definition is streamlined (since functions are so important)

- Native operations use prefix notation rather than infix notation which allows unambiguous function composition (read more here).

Here’s another example, this one more involved. These code samples find all words with 3 characters, lowercases them, and then sorts them:

// JavaScript const sentence = 'The quick brown fox jumps over the lazy dog' sentence.split(' ') .filter((x) => x.length === 3) .map((x) => x.toLowerCase()) .sort()

Clojure:

;; Clojure (let [sentence "The quick brown fox jumps over the lazy dog"] (->> (clojure.string/split sentence #" ") (filter #(= (count %) 3)) (map clojure.string/lower-case) sort))

Although these examples might look similar, there are some key differences. We already get to see some exciting elements that Clojure supports out of the box like the thread-last macro (->>),immutable types (Clojure’s `sort` does not mutate the list, whereas JavaScript’s `Array.sort` does), and streamlined collection operations (JavaScript’s `.map` and `.filter` functions are part of the Array class, whereas Clojure’s `filter` and `map` can work on any collection).

The REPL

“Many Clojure programmers consider the REPL, and the tight feedback loop it provides, to be the most compelling reason to use Clojure. This does not mean that the language features of Clojure, such as immutable data structures, are not valuable: the Clojure REPL gets most of its leverage because of these features, in particular because Clojure was designed with interactive development in mind.”

— Clojure.org

Chances are, you have used a Read-Eval-Print-Loop (REPL) before. However, Clojure’s development experience is unlike anything else out there. Developing software quickly and accurately is all about reducing feedback loops. You want to make changes and see how those changes affect the system as quickly as possible. With many languages, developers achieve this through running tests with an autoloader or hot reloading code into a running service.

There are many ways in which this process can be slow and frustrating. Perhaps you have repeatedly sprinkle print statements throughout the code, wait for the code to reload, and try your request again. Sometimes, you may want to do something more experimental, like make a new request to a database and inspect the data, but to do this experiment requires making a new entrypoint into your system (like a new route or a new test).

There are many more scenarios like these that developers experience day-to-day, slowing them down and interrupting their flow. Again, Clojure’s docs say it best:

“Fundamentally, the reason programmers use the REPL for all these tasks is always the same: because they want a mix of automation and improvisation that can be provided neither by fully manual tools (such as dashboard, consoles, etc.) nor by fully automated ones (such as scripts), while keeping their workflow focused in one full-featured programming environment.“

With Clojure’s REPL, developers can connect to it from nearly any editor (our team uses Emacs, Vim, VSCode, Cursive, and a few others). This unifies the code you’re writing with a live environment. You no longer have an editor where the code is static, separate from the running environment. The code and the environment become one, changing the experience of writing code to one where it’s more like you’re engineering a system while it lives and breathes.

Something even more mind blowing: this is not just for local development. You can connect to the REPL in a live, production service. Our team does this to debug issues and introspect our services on a regular basis. With this power comes great responsibility, so use it wisely.

Big, Stable Ecosystem

When Clojure was created, it launched with an enormous ecosystem. Clojure has a cheat code in this regard, which is that it’s built on top of the JVM**. Need to use a library that’s only available in Java? No problem.

Over the years, the Clojure community has built a wide array of libraries and services on top of what was naturally available via Java. Nowadays, you can readily find Clojure libraries to create web services, interact with external APIs, connect to databases… pretty much anything you need.

Something truly remarkable about Clojure is not just that it has a huge ecosystem, but that Clojure itself does not introduce any breaking changes. Code written ten years ago works just as well today. Many Clojure libraries are also remarkably stable, due to stability being a core value the Clojure community holds dear.

—

Clojure has a lot to offer and with each day, I grow more excited about its future at Crossbeam. Our teammate Simon summarized things quite well:

There are two things as satisfying as loading a fresh Clojure namespace. First, is when you are having trouble with your IDE and the author of the IDE helps you in the Clojurian’s slack channel (this happened to me with Cursive). The second is when you use the REPL to quickly build production ready code and see it ship with no bugs.

If you’re looking to work with great engineers in this language, we’re hiring!

* Clojure code is not just uniform, it’s homoiconic. The structure of the code matches the interpreter’s internal representation. This is a neat feature that makes macros possible.

** Since its inception, several additional runtimes for Clojure have been introduced. ClojureScript, most notably, is Clojure running on top of JavaScript, meaning you can build frontend UIs in Clojure. Babashka is a Clojure interpreter that leverages GraalVM. Its main use case is for scripting, where startup time is very important.