Learning Clojure — Interactive Programming and Deliberate Practice
Why Learn Clojure?
This month, I have started a new chapter since Juntos has been acquired by Nubank! Among the many, many things I find exciting about Nubank is that their tech stack is primarily built on top of Clojure. To try to hit the ground running, I spent much of the past month trying to learn Clojure.
The goal of this post is to share some of the strategies that I found helpful in my learning journey in the hope that it might be of use to others who also decide to learn Clojure. In particular, I focus on how embracing Clojure’s interactive programming flow, REPL-Driven Development, accelerated my learning by enabling deliberate practice.
The rest of this post is organized as follows:
- Overview of resources for getting started with Clojure
- Brief definition of REPL-Driven Development
- Overview of how to combine resources with REPL-Driven Development to enable Clojure deliberate practice
- Appendix: Local setup for Excercism + REPL-Driven Development in Vim
Resources for getting started with Clojure
Since the time that I began trying to learn Clojure, Peter Strömberg has created a new Getting Started with Clojure guide that I would recommend over the resources that I used. It leverages Gitpod and Calva to allow for REPL-Driven Development right from your browser with zero installation. In the next section, I’ll define REPL-Driven Development, but first I’ll briefly describe the resources I used just to give a sense of what else is out there.
When I started my learning journey, I began by reading the official Learn Clojure guide, which provides a concise overview of syntax, functions, and data structures. For my first hands-on-keyboard practice, I then turned to the elementary exercises on 4Clojure.com. As it turned out, I was one of the last to get to learn on 4Clojure.com since it was shut down at the end of July 2021. However, Peter Strömberg has also created a new version named Rich 4Clojure that, like the Getting Started guide and unlike the original 4Clojure, allows for REPL-Driven Development with zero installation. Another nice addition is that Rich 4Clojure allows you to see the tests used to check your solution. And while Rich 4Clojure is working to build up a new history of community solutions, 4ever-clojure, another copy of the original 4Clojure, allows you to view the solutions archive from the original site.
Since the original 4Clojure didn’t allow for REPL-Driven Development within the browser, when I decided to try to learn via REPL-Driven Development I turned to Exercism.io. I first learned about Exercism when my friend Angela blogged their praises. Getting set up for REPL-Driven Development via Exercism on your machine does include installation, which is why I now recommend Peter Strömberg’s Getting Started with Clojure guide instead. For those who do want to try Excercism with Vim, I’ve listed the setup steps at the end of this post.
REPL-Driven Development
In lurking on the very friendly Clojurians Slack, one of the questions that caught my eye was a request for advice about what to practice as a beginner who wants to become a more productive Clojure programmer. The first answer from Sean Corfield was to learn how to leverage the Clojure interactive programming workflow known as REPL-Driven Development.
The REPL (short for “Read, Eval, Print, Loop”) enables interactive programming in Clojure by allowing for expressions to be evaluated one at a time. In Sean’s pithy summary, REPL-Driven Development involves adherence to two maxims:
- Always work in your editor, never type into the REPL.
- Always evaluate every change you make as you make it.
Clojure Deliberate Practice
Using REPL-Driven Development to solve problems, like those from 4Clojure and Exercism, accelerated my learning because it enabled deliberate practice. The term deliberate practice comes from the research of psychologist K. Anders Ericsson who studied people achieve expertise. Ericsson argued that the key to achieving expertise in any domain is “deliberate practice,” which “entails considerable, specific, and sustained efforts to do something you can’t do well—or even at all.” More than just exerting effort on a challenging task, Ericsson defined deliberate practice as requiring that:
- “The design of the task should take into account the preexisting knowledge of the learners.”
- “[Learners] should receive immediate informative feedback and knowledge of results of their performance.”
Ericsson’s research has received some fair critiques,1 but I have nonetheless found it a useful framework for explaining why I have found that solving problems like those from 4Clojure and Excercism with a REPL-Driven Development workflow has been effective for learning Clojure.
In particular, this combination facilitated deliberate practice for me in five ways:
-
The problems on 4Clojure and Excercism are specifically targeted at beginners and designed to help them incrementally learn the fundamentals of Clojure so that I was always stretching just beyond my comfort zone.
-
Working inside an interactive development environment, like Gitpod + Calva in your browser or any of the number of options for local IDEs (IntelliJ + Cursive, Emacs + CIDER, Vim + vim-fireplace, Neovim + Conjure), helped to build my knowledge by making it easy to look up the documentation for functions as I encountered them for the first time.
-
Using a REPL-Driven Development workflow allowed for immediate informative feedback since I could evaluate each incremental code change.
-
The fact that Rich 4Clojure and Excercism allowed me to see the tests provided feedback on edge-cases that I should consider and helped me to learn to write good tests in Clojure.
-
The ability to view community solutions on 4ever-clojure and Exercism.io gave me feedback about how to make my solutions more idiomatic, allowing me to learn from the accumulated expertise of the entire Clojure community.
I’m still far from an expert and, even after I’ve had a chance to practice a lot more, there will always be more to learn. But I’ve come a long way from where I started and I’m excited to continue the journey.
Appendix: Local setup for Excercism + REPL-Driven Development in Vim
Before I began, I was intimidated by the prospect of setting up my local development environment for REPL-Driven Development. Partly, I feared it would be time-consuming and confusing. Partly, I hesitated because I was unsure whether I should switch from Vim to Spacemacs so that I could use the popular CIDER interactive development environment.2
While I hesitated, I spent a lot of time typing into the REPL.
Ultimately, I chose to just stick with Vim and set up vim-fireplace to connect to a REPL from my editor and I’m very glad I did! I plan to try out Neovim + Conjure in the future but I’m glad that I chose to start with vim-fireplace since it allowed me to just focus on learning Clojure but at a much faster rate (which is what multiple Redditors recommended).
In case it helps inspire others to take the leap, below is an outline of how I set up my local development environment for learning Clojure with Exercism and Vim. It’s not intended to be a step-by-step walk-through since specific instructions might go stale.3 Instead, the goal is to list the basic steps in the hope of reinforcing that it really isn’t that complicated.
Installing Exercism
This was really easy thanks to Exercism’s excellent walkthrough.
Install Clojure
Following Exercism’s advice, I set up Clojure by installing Leiningen via Homebrew. (Leiningen is a build tool that allows you to run Clojure code and, importantly, provides access to a REPL.)
Install cider-nrepl
This step was a bit confusing at first but would up being simple. All I had to
do was create a ~/.lein/profiles.clj
and then paste the
suggested minimal
profiles.clj
. (CIDER nREPL is
a “collection of network REPL middleware” that
enhances vim-fireplace.)
Install vim-fireplace
Also, a little bit confusing since the README for
vim-fireplace simply says “Install
Fireplace using your favorite package manager, or use Vim’s built-in package
support,” and then gives step-by-step instructions for the latter but no
instructions for the former. However, again, it was simple in the end, since I
just had to add a
line
to the plugins section of my .vimrc
.4 (vim-fireplace is what
allows Vim to talk to the nREPL so that you can evaluate Clojure code without
having to leave Vim.)
Install clj-kondo for linting
I love code linters. I learned more about how to write idiomatic Python and Ruby from flake8 and RuboCop, respectively, than from any other single source. I chose clj-kondo since I’m using the Vim plugin ALE and clj-kondo is one of two linters that ALE supports for Clojure.
I used Homebrew to install
clj-kondo
and then
added
clj-kondo
to the list of ALE linters in my .vimrc
Hello Excercism + vim-fireplace
Now that your setup is complete, here’s a short example using Exercism’s Hello World exercise for Clojure to illustrate how a REPL-Driven Development workflow facilitated by vim-fireplace can be used to solve Exercism problems and get the kind of feedback that is central to deliberate practice.
First, start a terminal session and download the exercise via the Exercism CLI tool. (When viewing the exercism on Exercism.io, there is a helpful button to copy this code.)
$ exercism download --exercise=hello-world --track=clojure
Second, start another terminal session (either in a new window or a new tab if using an emulator like iTerm2), change to the directory containing the Leiningen project for the exercise, and begin a REPL.
$ cd ~/Exercism/clojure/hello-world
$ lein repl
Next, return to your first terminal session and use Vim to open up the file where you’ll encode your solution.
$ cd ~/Exercism/clojure/hello-world
$ vim src/hello_world.clj
You should now see a file that looks like this:
(ns hello-world)
(defn hello [] ;; <- arglist goes here
;; your code goes here
)
1. Getting Feedback From Documentation
My own first attempt at this solution looked like this:
(ns hello-world)
(defn hello [] (println "Hello, World!"))
Before I even try to evaluate this function, vim-fireplace allows me to get feedback by looking up documentation without having to leave my editor. The author of vim-fireplace, Tim Pope, was brand new to Clojure when he starting working on the plugin so it includes the following mappings for understanding code:
K
is mapped to look up the symbol under the cursor withdoc
[d
is mapped to look up the symbol under the cursor withsource
If I place my cursor at the beginning of println
and hit K
in normal mode, I
can read the documentation for println
to see if it is what I want. The
output, in the bottom left-hand corner of my screen will say:
-------------------------
clojure.core/println
([& more])
Same as print followed by (newline)
I thought that was what I wanted so the next step was to evaluate the function itself.
2. Getting Feedback From Evaluating Functions In Your Solution
Before I was set up for REPL-Driven Development, I would evaluate functions by
copying and pasting them into the REPL running in the other terminal. However,
with REPL-driven development, there’s an even better way. With my cursor inside
(println "Hello, World!")
, from normal mode if I type cpp
, vim-fireplace
evaluates the code and shows me the result in the bottom left-hand corner. In
this case, the result is:
Hello, World!
nil
This is clearly more efficient! But I was initially confused by the result. Then
I remembered that println
returns nil
and I want a function that returns
"Hello, World!"
rather than printing it. Using this learning, I re-wrote my
function to look like this:
(ns hello-world)
(defn hello [] "Hello, World!")
To test this with vim-fireplace, I can execute cpp
inside my defn
to define
the hello
function and then execute cpp
inside (hello)
to see what’s
returned. Now all I see returned is "Hello, World!"
. Looking better!
I can then use the vim-fireplace normal mode command :Last
to open the
result in the preview window so that I can yank (copy) and then put (paste) the
result below the function into a section named “REPL Experiments” that leverages
Clojure’s rich comment
blocks.
(ns hello-world)
(defn hello [] "Hello, World!")
;; * REPL Experiments *
(comment
;; Check the return value of `hello` function
(hello)
)
I’ve found this practice of documenting REPL experiments helpful when iteratively working to solve multi-part problems. My solution to the Run Length Encoding problem includes an example of this.
3. Getting Feedback From Evaluating Functions In The Tests
To confirm that is what the test is expecting, I can open the test file
(:e test/hello_world_test.clj
) which looks like this:
(ns hello-world-test
(:require [clojure.test :refer [deftest is]]
hello-world))
(deftest hello-world-test
(is (= "Hello, World!" (hello-world/hello))))
The fact that Exercism allows me to see the test helps make the exercise more targeted since it tells me exactly what my objective is. Given that I had never written a test before in Clojure, seeing examples of how to write concise but comprehensive tests is also a learning in and of itself. Additionally, as the problems get more complex, the tests have helped me think about edge cases that I wouldn’t have considered.
Using the same trick as before, I can evaluate the test without leaving my
editor by adding a line that calls the test function ((hello-world-test)
) and
executing it by typing cpp
. It returns nil
, which means it passed! To
double-check, I can now exit my editor and run the test with lein test
which
will output:
lein test hello-world-test
Ran 1 tests containing 1 assertions.
0 failures, 0 errors.
4. Getting Feedback From The Community
Now, I’m ready to submit my solution:
$ exercism submit src/hello_world.clj
This will return a link to an Exercism page where I can view my solution and, more importantly, also get to see community solutions. Seeing others’ solutions has been a great way for me to learn about alternate approaches that I hadn’t considered and get a sense of whether my solution could be made more idiomatic.
-
This blog post has a concise but nuanced summary of the criticisms of Ericsson’s research. For an academic summary see Hambrick et al. (2020). ↩
-
I switched from Sublime Text to Vim around the beginning of 2021 and had been getting a lot of joy out of learning Vim. However, I learned from the State of Clojure 2021 Survey that Vim is a distant fourth in the ranking of the most popular editors for Clojurians (at 8.66% of respondents), with Emacs + CIDER (44%) and IntelliJ + Cursive (31%) being the two most popular. ↩
-
In fact, as I write this on August 28, 2021, we are days away from the launch of Exercism v3 which will introduce a new online editor that enables solving exercises directly in the browser without setting up anything locally, though the CLI tool will remain. ↩
-
I initially agonized over which Vim plugin manager to use. (Astute readers might be noticing a pattern.) I have been happy with my choice of vim-plug but think Vundle or Pathogen would have been fine too. ↩