Share the nitty-gritty details of your Clojure workflow!

I have two main projects right now. One is an CLJS app for work, the other is an open-source library that I primary develop using CLJ (it supports CLJS, but most of my development is in Clojure). My answers are slightly different for each.

:bmo: What kind of REPL do you use—command-line, CIDER’s REPL buffer, or integrated with your project files? If a file, which ones?

CLJS-app: I start lein repl in the terminal. I then connect with cider-connect and then run

(use 'figwheel-sidecar.repl-api)
(start-figwheel!)
(cljs-repl)

My workflow might be out-of-date here, but I found that more CIDER features work when I do this - namely code navigation.

CLJ-lib: I start lein repl and use cider-connect.

In both projects, I then will create a (comment) block and start evaluating forms directly from my editor with cider-eval-last-sexp (bound to a keybinding, of course). The results show up inline in my editor.

Occasionally the results are too big too understand. I’m not too good at navigating the cider-inspect-last-result buffer, so instead I run cider-eval-last-sexp-and-replace to put the data right in my comment block. I can then use it directly in subsequent experiments, or name it with let or def.

From there, I tend to build up my expression with lots of tiny experiments, and then extract it to a function.

If I’m investigating a bug with an existing function, I tend to use some combo of:

  1. Adding println (usually prefixed with my initials like [:bhb {:x x :y y}] so I can grep for “bhb” to remove all debugging lines)
  2. Creating an example of the function call, then slowly comment out parts of the function implementation so I can see each step returned.
  3. Sometimes I’ll use “def” to define vars that mirror the local bindings, and then directly evaluate the inner sexps. I usually do this manually, but I have been trying to use scope-capture more to automate this.

I tend to run git commit -am "cp" (“cp” == “checkpoint”) often, so I can commit experiments, messy code, printlns, etc. When I have a logical piece of work to commit, I’ll clean it up, git reset <sha> to the last “real” commit, and then make a single commit of the total changes.

I use both Emacs+CIDER and Cursive: I write/edit nearly all my code in Emacs+CIDER, but occasionally open Cursive to look at warnings and also for renaming symbols and keywords.

:sunrise_over_mountains: How do you deal with state in the REPL, such as let bindings or other local vars?

This hasn’t historically been much of a problem for me. In CLJS, I can just refresh the browser if I notice a problem, which is rare. And in CLJ (perhaps because my project is a library without much state), I rarely run into bugs with stale state. In the rare cases where this occurs, my tests usually catch the issue quickly (see below)

:paperclip: Do you save your REPL explorations—other than any “official” result like a def’d function—off to a file? Where? Does that file go into source control?

I don’t normally save the REPL explorations. For experimentation outside of any specific project, I’ve been toying with a single project with a single namespace that is just a long log of REPL experiments. That project contains all the various tools I might use and I commit all my experiments, but it’s not part of my normal flow for either of my daily projects.

:books: Do you write tests directly from what you write at your REPL, or separately? From where you run those tests—the shell, CIDER, only on a CI server?

I do convert some of my REPL experiments to tests.

CLJS-app: I run tests on the terminal with karma. They re-run every time I save a file

CLJ-app: I run tests with lein test-refresh. They re-run whenever I save a file.

Although this feedback cycles isn’t as fast as I’d like (especially the CLJS tests, the CLJ tests are pretty snappy), I do like that this protects me somewhat from the occasional bugs resulting from stale state in my REPL.

I also run the full suite in CI, just as a final check (this often catches things like forgetting to commit a file :grimacing:)

1 Like