A Clojure Development Environment That Gets Out of Your Way

A Joyful Introduction to Clojure, Part I

Daniel King
17 min readOct 6, 2018

Part II >>

One of the hardest things about getting started with Clojure is setting up a development environment that allows you to get the most out of the language with a minimum of fuss.

For starters, you have to choose an editor. The most popular choices in the Clojure world are Emacs, Vim, and IntelliJ. Most Clojure tutorials will recommend one of these, because the ecosystem of Clojure tools and plugins around these editors is extremely high quality and mature.

We’re not going to use those. Instead, we’re going to use Atom, because

  1. It is extremely straightforward to download and install.
  2. It is extremely usable without learning lots of new concepts and hotkeys — it will let you focus on learning Clojure, not your editor.
  3. Of the popular lightweight editors (Sublime, Atom, and VS Code), Atom has the best Clojure support, due to the awesome Proto REPL project.

Next, you have to choose a command line tool. There are three popular options here: Leiningen, Boot, and the new official Clojure CLI. Most Clojure tutorials will recommend Leiningen, because it has been the build tool of choice for most of Clojure’s history, and it’s ecosystem of tools and plugins is extremely high quality and mature.

In this case, we’re going to go with the conventional wisdom and use Leiningen.

Let’s dive in.

Disclaimer: The specific steps described in this guide are geared toward users on Unix-like systems. If you are on Windows, you will need to adapt file paths and installation instructions for your own system. The Clojure parts should be the same, though.

Update 11/02/2018: A previous version of this article recommended the Clojure CLI, but I decided to revert back to using Leiningen because it will make our lives easier down the line when we do more advanced project configuration.

Step 0: Installing tools

Leiningen and Clojure both require that Java is installed on your machine. You can check this by typing which java in your terminal. If you get an output like /usr/bin/java , you are good to go. Otherwise, you will need to install OpenJDK 8 on your machine.

The Leiningen website has manual installation instructions for Leiningen. If you’d rather, you can also install Leiningen through the package manager on your system (for example, on Mac you can use brew install leiningen ).

Once you have installed Leiningen, you can check to see if everything went according to plan by typing which lein in your terminal. You should get an output like /bin/lein (or wherever your Leiningen executable is located).

Step 1: Baby’s First Clojure

At the most basic level, the Clojure development environment is composed of two tools:

  1. The Clojure compiler, which translates Clojure code into bytecode for the JVM (Java Virtual Machine)
  2. The Clojure REPL (Read-Eval-Print Loop), which allows you to interactively work with the Clojure compiler. Expressions typed into the REPL are compiled on the fly, the resulting bytecode is executed on the JVM, and then the outputs are printed back in the REPL prompt.

Leiningen is essentially a wrapper that manages the low-level details of these tools for you. When you use Leiningen, you are just using the Clojure compiler and REPL behind the scenes, without needing to fuss with configuration details.

Let’s write your first Clojure code! Fire up an interactive Clojure REPL session by typing lein repl in your terminal. You should see a prompt like:

nREPL server started on port 34616 on host 127.0.0.1 - nrepl://127.0.0.1:34616
REPL-y 0.3.7, nREPL 0.2.12
Clojure 1.9.0
OpenJDK 64-Bit Server VM 1.8.0_181-8u181-b13-1ubuntu0.16.04.1-b13
Docs: (doc function-name-here)
(find-doc "part-of-name-here")
Source: (source function-name-here)
Javadoc: (javadoc java-object-or-class-here)
Exit: Control+D or (exit) or (quit)
Results: Stored in vars *1, *2, *3, an exception in *e
user=>

One of the great things about Clojure is that its syntax is ridiculously consistent. Every operation, every function call, every instruction follows the exact same format:

(operator argument argument [...more arguments])

If you’re used to other languages, this can look strange until you get used to it. For example, to add 2 plus 2, we do this:

(+ 2 2)

Notice that the operator comes first, as it always does in Clojure. Try typing this in and evaluating it in your REPL prompt:

user=> (+ 2 2)
4

Well, at least the Clojure compiler can do math correctly.

In any programming language, we need a way to bind names to values. Many languages use the = operator for this purpose, but Clojure calls this operation def instead:

user=> (def my-name "Daniel King")
#'user/my-name

Once you have done this, the symbol my-name is bound to the string value that you provided:

user=> my-name
"Daniel King"

As you would expect, we can also define our own functions. This is done with the defn operator, whose usage looks like this:

(defn function-name [arguments] body)

Let’s create a function in your REPL (make sure you get all the parentheses in the right places):

user=> (defn hello-message [name] (str "Hello " name))
#'user/hello-message

We can then call your function, and pass in the my-name value you defined earlier as an argument:

user=> (hello-message my-name)
"Hello Daniel King"

A few things to notice about this:

  1. Clojure has lots of nested parentheses. It’s a pain to type all the close-parens in the right places, but your editor will take care of that for you once we set it up.
  2. The str function concatinates strings together, similar to how the + operator works on strings in many other languages.
  3. There is no return keyword in Clojure. A function automatically returns the value of the last expression in the function body, in this case the concatination of "Hello “ and my-name .
  4. I have been carefully avoiding using the word “variable”. In most languages, doing num = 3 is called “assigning a value to a variable”. In Clojure, doing (def num 3) is known as “binding a name to a value”. The symbol num is not a “variable”, because it will not vary once it is initially bound.

We’re done with the REPL for now, so go ahead and stop it by typing quit. Now that we have the Clojure compiler up and running, let’s get the rest of the development environment set up.

Step 2: Setting up Atom for Clojure development (an opinionated guide)

You will not be surprised to learn that the first step is to download and install Atom if you don’t already have it.

Once you have Atom installed, we need to do some customizing.

Intalling Atom packages

Open the settings page by going to Edit > Preferences, and click on the Install tab on the left side of the screen:

Installing packages in Atom

You will need to install 3 packages: ink, lisp-paredit, and proto-repl. Once these are installed, restart Atom.

Tweaking package settings

Go to the Packages tab, search for “bracket-matcher”, and open the settings page for the bracket-matcher plugin:

Find the “Autocomplete Characters” option, and remove the single apostrophes and backticks. It should look like this when you’re done:

We did this because Clojure uses individual apostrophes and backticks, unlike most languages which use them in matching pairs.

Next, open the settings for the proto-repl plugin:

Uncheck the “Ink Console” option. This turns off some of the fancy graphical effects for Proto REPL, but improves the functionality. We’re here to get work done, not marvel at pretty graphics.

Adding local dependencies to Leiningen

To allow Proto REPL to communicate with your Clojure project, we have to declare a dependency on the proto-repl library.

Find the file ~/.lein/profiles.clj , or create it if it doesn’t exist. Then, copy-paste the following data into this file:

{:user {:dependencies [[proto-repl "0.3.1"]]}}

Let’s break this down a bit.

In Leiningen, configuration is done through the use of “profiles”, which allow you to collect groups of configuration options under a name. Then, when you run Leiningen, you specify which profiles you want to use. Leiningen will then merge all of the configuration options contained in those profiles.

Normally, Leiningen profiles are defined in the configuration files for a single project. However, Leiningen will also look in ~/.lein/profiles.clj for available profiles, which makes this a good place to put dependencies needed by the development environment on your local machine.

There is a special profile called user which is always turned on by default. In the configuration above, we are declaring that when the user profile is used, we should include a dependency on proto-repl version 0.3.1.

The result: Whenever you are working on any Clojure project on your local machine, Leiningen will always include a dependency on proto-repl version 0.3.1.

Try running lein repl again in your terminal. You should see the new dependency being downloaded before the REPL starts. Then, you can stop the REPL by running quit.

Story time (a cautionary tale): When I was first learning Clojure, I went through a tutorial that told me to declare a dependency in ~/.lein/profiles.clj like we’ve done here. I then forgot that it was possible to declare dependencies there. About a year later, I was working on a side project in Clojure, and I started getting strange errors whenever I tried to start my REPL. Ultimately, after a couple of hours of searching, I happend to stumble across ~/.lein/profiles.clj and noticed that the dependency it declared was out of date. The errors were happening because one of the newer libraries I was using in my project was incompatible with the old dependency version declared in ~/.lein/project.clj . If I had remembered that ~/.lein/profiles.clj existed, it would have taken me minutes, not hours, to find this issue and fix it.

The moral of the story: Remember that ~/.lein/profiles.clj exists, so that you can update those dependencies to newer versions as necessary.

At this point, Atom is fully set up for Clojure development. Let’s work on something bigger, so you can see how all the tools fit together.

Step 3: A full Clojure project

The code samples for this series of articles live in this Github repository. Get it onto your local machine by doing one of the following:

  1. Fork and clone the repository (preferred method)
  2. Download the repository as a zip file (alternate method if you are not confident with Git and Github)

If you forked and cloned the repository, make sure that you also set my version of the repository as a remote, so that you can pull updates as necessary:

cd /path/to/joyful_clojure
export CLJ_URL=https://github.com/dking1286/joyful_clojure.git
git remote add upstream $CLJ_URL

Then, use Atom to open sample project 1:

atom /path/to/joyful_clojure/01_clojure_development_environment

Let’s look at a couple of the files before we start changing things. First, the project.clj file:

(defproject joyful-clojure-01 "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.9.0"]]
:source-paths ["src"]
:repl-options {:port 8081})

The project.clj file is the main configuration file for Leiningen. It is roughly like the package.json in a Javascript project: It declares the dependencies for the project, and includes some other important configuration information.

The :source-paths key specifies where the Clojure compiler will look for the source files in this project. In almost all Clojure projects, a directory called src is used for this purpose.

As in the ~/.lein/profiles.clj file, the :dependencies key shows the dependencies of the project. Right now, the only dependency we have declared is the Clojure compiler itself, version 1.9.0.

We’ll come back to the :repl-options key in a moment.

Next, open the src/main.clj file:

(ns main)(def my-name "Daniel King")(defn hello-message
[name]
(str "Hello " name))
(defn -main
[& args]
(println (hello-message my-name)))

Every Clojure file starts with a call to the ns operator, which declares the “namespace” created by the file. The namespace must match the name of the file.

Notice that we are defining the same my-name and hello-message values that we defined in the terminal REPL earlier. We have also added a -main function, which is the entry point to the entire project when we compile and run it.

In your terminal, try running the following:

lein run -m main

You should see something like Hello Daniel King printed in your terminal.

Here’s what just happened: When we use the command lein run , Leiningen will compile and run the whole project. It does so by performing the following actions:

  1. Look at the namespace specified by the -m command line option. This namespace will be the entry point of the project. In our case, this is the main namespace declared in main.clj.
  2. Find the function named -main in the namespace declared in step 1.
  3. Compile the whole project and run the -main function, passing in any arguments given on the command line.

Now that we have a working project, the last step is to connect Proto REPL to your project. This is where the magic happens.

Step 4: Live development with Proto REPL

Use lein repl to start a REPL in your terminal again. When the REPL starts, notice the following line that gets printed:

nREPL server started on port 8081 on host 127.0.0.1

nREPL is short for “network REPL”, and it is a protocol that allows other processes to send commands to a Clojure REPL through a network port.

This opens up lots of interesting possibilities: You could expose an nREPL port on an application in production, and then use it to debug your application in real time while it is running.

Clojure-aware editors also make use of nREPL. Instead of attempting to parse your source code to provide editing features like context-aware autocomplete, the editor just connects to a running REPL through an nREPL port, and then uses that connection to determine everything it needs to know about the project.

Remember that we put :repl-options {:port 8081} in your project.clj file. When you start a REPL with lein repl , the nREPL server will listen for connections on this port.

Let’s connect Atom to the nREPL server listening on port 8081. With the 01_clojure_development_environment project open in Atom, open the Atom command palette (Ctrl+Shift+P on Linux, probabyl Cmd+Shift+P on Mac) and search for the command “Proto Repl: Remote Nrepl Connection”:

Run this command, and Atom will prompt you to choose a host and port for the nREPL client. It should be able to auto-detect the correct host and port of the nREPL server, but if not, you can enter them manually:

Press enter, and you should see a Clojure REPL window pop up:

In this REPL window, you can evaluate Clojure code, just like we were doing earlier in the terminal (note that you use Shift+Enter to evaluate code):

That’s all well and good, but we haven’t yet done anything that you couldn’t do in your terminal. The real magic is that you can also navigate around your project’s source code in this REPL.

The REPL always starts in a namespace called user by default. Notice the user=> that appears each time you evaluate code in the REPL — this means that the REPL is evaluating code in the context of the user namespace. However, our code is currently located in the main namespace, as indicated by the ns operator in our source file. Therefore, the REPL cannot currently “see” our source code, because the REPL is looking at a different namespace.

To navigate the REPL into the main namespace where our code is located, enter (in-ns 'main) in the REPL (note the single apostrophe before the namespace name — we’ll come back to what this means later):

You should see the user=> change to main=> indicating that the REPL will now evaluate code in the context of the main namespace defined by our source file. Once you’ve done this, the REPL has access to all of the values and functions defined in this source file:

This allows us to quickly run our functions as we’re developing them, observe their behavior, and correct them. It allows us to be intimately connected to the code in a way that most other tools can’t match, because the feedback loop is so wonderfully, beautifully short.

One last thing: If you change your code, the REPL can immediately refresh and pick up those changes. However, you need to tell it when you want it to refresh. Luckily, Proto REPL has a command called “Refresh namespaces” that allows us to do this easily (on my machine, the hotkey is Ctrl+, r. Look in the command palette to check the hotkey on your system).

With all this in mind, here’s a summary of the end-to-end workflow:

  1. Start a REPL in your terminal with lein repl
  2. Open your project in Atom, and connect to your REPL using “Proto Repl: Remote Nrepl Connection”
  3. Navigate the REPL into the namespace you are working on with in-ns
  4. Add new functions and values, or change the existing ones
  5. Refresh the REPL using “Proto Repl: Refresh Namespaces”
  6. Try out the functions in the REPL to get feedback on your work
  7. Repeat 4, 5, and 6 until the work is done

Frequently asked questions

Q: This REPL-driven development thing seems pretty cool, but I like to do TDD. When should I use the REPL in development, vs. using a test runner like I would in another language?

A: The awesome thing is that you don’t have to choose: The REPL is your test runner during development. I’ll show how this works in a later article in this series, but for now, note that Proto REPL has commands called “Run tests in current namespace” and “Run all tests”. When I’m developing Clojure, I typically use these commands to run my tests, rather than relying on an external test runner that watches my files for changes.

Leiningen can also run the automated tests for your project using the lein test command. This command is mostly used to run tests in automated environments, like Continuous Integration pipelines, rather than in your local development environment.

Q: No, seriously, what’s the deal with the single quote in (in-ns 'main) ? It’s confusing, and unlike anything I’ve seen in any other language.

A: This opens up a very deep conversation on the “metaphysics” of Clojure, but I’ll try to give you a sense of what it means without getting too far into the weeds. Feel free to skip this answer and come back to it later, if it suits you.

Many programming languages have a function called eval or something similar, which allows you to take a piece of data and interpret it as executable code. For example, in Javascript, the eval function takes a string and executes it as code:

const unevaluated = "console.log('Hello world')";
eval(unevaluated) // Prints "Hello world" to the console

Clojure also has an eval function, like all Lisp dialects. However, it also has the “quote” operator, which is the opposite of eval : It means “don’t evaluate this”.

Notice that when I entered (+ 2 2) into my REPL, the expression was evaluated to 4 , but when I entered '(+ 2 2) , the result was the unevaluated list (+ 2 2) .

The equivalent of the Javascript code above in Clojure would look like this:

One last thing to notice before we table this discussion for later: In Clojure, there is a distinction between strings (represented by matched " characters) and unevaluated code (represented by a single ' character). In Javascript, the eval function expects a string as an argument:

eval in Javascript

However, in Clojure, the eval function expects to receive a data structure representing the code to evaluate, not a string. There is a separate function called read-string that takes in a string and returns such a data structure:

eval in Clojure

To execute a string of code as we did in Javascript, we need to use both read-string and eval :

In fact, this is exactly what the REPL is doing under the hood: It is calling read-string on the text that you type in, then calling eval on the resulting data structure, then printing the returned value to the console. This is unsurprising in hindsight, since REPL stands for Read-Eval-Print Loop.

With all that said, let’s talk about (in-ns 'main) . Normally, when you evaluate a symbol like main , the Clojure compiler looks for a “variable” defined in the current namespace with that name (again, I am putting the word “variable” in quotes because they don’t vary). So, if we are in the user namespace and we evaluate (in-ns main) with no single quote, the Clojure compiler will look for a variable called main in the user namespace. This will cause an error, because there is no such variable:

We include the single quote to tell the compiler not to evaluate the symbol main , so that this error will not occur:

Surviving Clojure parentheses

When we were typing out Clojure code in the terminal, you might have noticed that it is tough to get all the parentheses in the right places, even for relatively small bits of code. This problem grows exponentially when the code gets more complex, like this (still relatively small) function from the Ring HTTP server library:

For this reason, it’s essential that our editor help us to manage all these parentheses. This is the purpose of the lisp-paredit plugin that we installed, a classic Emacs plugin that has been ported to most other major editors over the years.

By default, Paredit is configured to use “strict mode,” which will not allow you to delete a parenthesis if there is anything inside it:

Notice that I can’t delete the closing parenthesis unless there’s nothing left inside the matching pair.

This is extremely useful, because it prevents you from accidentally deleting a closing parenthesis somewhere, and then needing to hunt around among dozens of parentheses to find where there’s one missing.

However, this can also come back to bite you, if you don’t know how to handle it. Let’s see how.

Suppose that I’m writing a function that constructs a url to request an article from awesome-website.com . The url should look like this:

https://awesome-website.com/articles/<article-id>

I start writing my function:

Then, I realize that I need to concatinate the value of article-id into this string, so I try to use the str function:

Noooooo!

…and I realize that Paredit won’t let me delete the closing parenthesis on the str call to move the string inside the parentheses!

There are 3 ways we could handle this:

  1. Turn off Paredit strict mode (bad idea, don’t do this)
  2. Cut-and-paste the string into the parentheses (slow and awkward)
  3. Use the full power of Paredit (highly recommended)

Paredit has a command called “slurp forwards”, which moves the right-hand parenthesis forward to enclose the next value:

Once that’s done, we can concatinate the article-id into the string to finish the function:

You can find the hotkey for “slurp forwards” in the Atom Command Palette:

Paredit has a lot of functionality, but this one command constitutes 80% or more of my usage. It should be sufficient to help you survive Clojure’s parentheses.

That’s it! Our Clojure development environment is set up. In the next article, we will dive into the language itself, and learn how to get work done using Clojure’s awesome immutable data structures.

Part II >>

--

--

Daniel King

Professional Software Engineer and Educator, amateur Musician, armchair Personal Finance Expert