A Clojure Development Environment That Gets Out of Your Way
A Joyful Introduction to Clojure, Part I
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
- It is extremely straightforward to download and install.
- It is extremely usable without learning lots of new concepts and hotkeys — it will let you focus on learning Clojure, not your editor.
- 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:
- The Clojure compiler, which translates Clojure code into bytecode for the JVM (Java Virtual Machine)
- 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 *euser=>
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:
- 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.
- The
str
function concatinates strings together, similar to how the+
operator works on strings in many other languages. - 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 “
andmy-name
. - 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 symbolnum
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:
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:
- Fork and clone the repository (preferred method)
- 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:
- 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 themain
namespace declared inmain.clj
. - Find the function named
-main
in the namespace declared in step 1. - 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:
- Start a REPL in your terminal with
lein repl
- Open your project in Atom, and connect to your REPL using “Proto Repl: Remote Nrepl Connection”
- Navigate the REPL into the namespace you are working on with
in-ns
- Add new functions and values, or change the existing ones
- Refresh the REPL using “Proto Repl: Refresh Namespaces”
- Try out the functions in the REPL to get feedback on your work
- 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:
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:
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:
…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:
- Turn off Paredit strict mode (bad idea, don’t do this)
- Cut-and-paste the string into the parentheses (slow and awkward)
- 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.