2020 Clojure Development with Lein and Rebel-Readline

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 ...

Project Automation (Leiningen)

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:

  • It's the easiest way to get started setting up Clojure (in my opinion)
  • It's been around for a long time so has lots of capabilities
  • I now know it and don't want to retread old ground to switch

The trade-offs are that people find Leiningen complex, obtuse and slow. In it's defense:

  • It can be complex because it has a lot of capabilities (and the documentation isn't that great)
  • It's often obtuse and difficult to understand why it's failing if you don't realise that Lein itself is a Clojure program
  • It is slow because part of the design is that it starts a JVM to run Lein and then it runs your program in a new JVM - so it's starting up two JVM's by default which is slow. There are ways around this.

Installing Clojure

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.

  • Install OpenJDK
    Using the commands:
$ 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)
  • Install Lein
    Lein itself installs itself and a whole load of Java capabilities. The main thing to note is that it will download Java dependencies (using Maven) and put these into a ~/.m2 directory.
mkdir ~/bin
wget https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein
chmod u+x lein
lein
  • Test it's working
    To quickly test that it's working we can do:
$ 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.

Rebel-Readline REPL

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:

  • Command history
  • Multi-line editing
  • Syntax colouring
  • Completions
  • Eldoc type arglist
  • Inline evaluation

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}

Testing Rebel Readline

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

Rebel Readline Usage

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

Final thoughts

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.


Posted in Tech Sunday 01 March 2020
Tagged with vim tech clojure