For Clojure development I use both the REPL and Vim: generally I play in the REPL a bit when learning new things and then use my Vim environment when I'm doing something more complex. In this post I'm going to go through how I use Leiningen and Rebel Readline with the aim of providing clear instructions on how to create a good set-up from scratch.
As an aside, there are quite a few posts about how difficult it is to set-up your environment for Clojure development. It's definitely difficult to learn Clojure and Vim at the same time, or Clojure and Emacs! But, there are much easier options now. To get up and running as quickly as possible Cursive for IntelliJ, or Calva for Visual Studio Code are the way to go. They're both easy and work well - so stop reading this, go grab one of them and start playing!
If you're interested in a complete set-up then follow along ...
In every development language there's a need for project automation meaning things like starting a REPL, creating the right directory structure for an application and building a distributable binary. In Clojure that's fulfilled by Leiningen.
I will say, that there's a lot of energy behind the tooling that's recently been bundled into Clojure (Deps and CLI) with lots of recommendations for this approach.
However, Leiningen has been the defacto Clojure project automation tool for a while and according to the State of Clojure 2020 it's still used by ~80% of Clojure developers.
The position between the two approaches is that Deps/CLI is probably easier to use, whereas Leiningen is more fully featured. I'm using Lein because:
The trade-offs are that people find Leiningen complex, obtuse and slow. In it's defense:
Clojure is a hosted language, it runs on the JVM, so the first step is to get Java running. According to the Clojure site we want a "Java LTS releases (currently Java 8 and Java 11)". I'm on Linux (specifically Ubuntu) so for me the easiest way is to use the JDK that comes with my distribution: this prevents me from having to mess around with a self-install, or keep the system up to date myself.
$ sudo apt install default-jdk <installs the package> # check that you got Java 11 $ java --version openjdk 11.0.6 2020-01-14 OpenJDK Runtime Environment (build 11.0.6+10-post-Ubuntu-1ubuntu118.04.1) OpenJDK 64-Bit Server VM (build 11.0.6+10-post-Ubuntu-1ubuntu118.04.1, mixed mode, sharing)
mkdir ~/bin wget https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein chmod u+x lein lein
$ lein version Leiningen 2.9.1 on Java 11.0.6 OpenJDK 64-Bit Server VM $ lein repl <starts the REPL> (exit)
At this point we have Clojure and Lein up and running. Before we play with Lein more lets detour to getting Rebel-Readline running.
The default REPL (lein repl) works well enough, but it lacks things like command history. Being able to recall the previous bit of code, play around with it a bit and then try it again is really important in the REPL workflow - particularly when you're learning!
Bruce Hauman created Rebel Readline which is a nice REPL that automatically wraps the readline library so you get history along with other features:
There's a good video overview by John Stevenson
To tell Lein about this alternative REPL we need to create a ~/lein/profiles.clj. This file is used to configure Lein settings, the minimum settings for Rebel Readline are:
{:user {:plugins [; ... other plugins ... ] :middleware [; ... middleware go here ... ] :dependencies [; ... dependencies go here ... [com.bhauman/rebel-readline "0.1.4"] ;; nice REPL ] :injections [; ... injections go here ... ] :aliases {"rebl" ["trampoline" "run" "-m" "rebel-readline.main"]} } :repl { :repl-options {:init (require '[clojure.repl :refer :all])} } }
If you're new to Clojure the syntax can look pretty confusing: it's actually Clojure syntax where we have keywords and maps.
Essentially, we've told Lein that it should load Rebel Readline as a dependency and then we've created an alias command of rebl to start it. The repl-options part is telling Clojure that when it starts it should make some default tools available in the standard namespaces (e.g. doc).
According to on-line chat Clojure beginners often forget that they've put settings into ~/lein/profiles.clj causing confusion when there are conflicts later on. Nonetheless, if you want a particular capability in every project then putting it in your profiles.clj is the right thing.
Check on the Rebel Readlines Github site that "0.1.4" is the latest available version, and change it to whatever the latest version is.
I want Vim key bindings in the REPL so I create a ~/.clojure/rebel_readline.edn and add:
# default vim inset mode {:key-map :viins}
To use Rebel Readline we need a Lein project (you can't just call it from any directory in your system). We create a test-rebel-readline project:
$ lein new app test-rebel-readline cd test-rebel-readline lein rebl
This will start the Rebel Readline REPL where you can interact with Clojure code.
; REPL is loaded and shows a user=> prompt user=> :repl/help ;; shows the help text user=> (println "Hello World") Hello World nil
As it's wrapping the readline library and is in vi mode I get a 'command' mode and 'insert' mode by default: mostly I use $/0 to get to the start and end of a line. Catonmat has a nice cheatsheet for readline. Other commands are:
Keyboard Shortcut | Command |
---|---|
<up arrow> | Previous line of input |
<Tab> | Indent the line or accept the suggestion |
<ctrl>x<ctrl>a | Apropos at cursor point |
<ctrl>x<ctrl>d | Clojure doc at cursor point |
<ctrl>x<ctrl>e | Eval at cursor point. |
<ctrl>x<ctrl>s | Source at cursor point. |
<ctrl>e | Accept the line (ctrl-j) |
<ctrl>d | List choices |
<ctrl>n | Move forward through choices |
<ctrl>p | Move backwards through choices |
<ctrl>_ | Undo |
<ctrl>r | Backward history search |
<Home> | Start of a multi-line input |
<End> | End of a multi-line input |
:repl/quit | Quite the REPL |
This is a really nice environment for playing around with Clojure. I often start in the REPL when learning a new concept by creating a function, trying it out and then editing it interactively to iterate on some aspect. It's a standard REPL so has all the same capabilities - in a future article I'll look at the REPL workflow a bit more.