Skip to content

Instantly share code, notes, and snippets.

@cstby
Last active January 17, 2022 23:12
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cstby/0cff1f7eb48eb18c6690e6cdb919ca69 to your computer and use it in GitHub Desktop.
Save cstby/0cff1f7eb48eb18c6690e6cdb919ca69 to your computer and use it in GitHub Desktop.
Faster feedback in Clojure with `cider-eval-n-defuns`.

Summary

You can use this Emacs function to easily evaluate multiple top-level forms in Clojure using CIDER.

(defun cider-eval-n-defuns (n)
  "Evaluate N top-level forms, starting with the current one."
  (interactive "P")
  (cider-eval-region (car (bounds-of-thing-at-point 'defun))
                     (save-excursion
                       (dotimes (i (or n 2))
                         (end-of-defun))
                       (point))))

Why?

A tighter feedback loop.

One of Light Table's unforgettable features was the Instarepl, which evaluates code as it's typed. Despite having almost no regular users in 2020, Light Table continues to be discussed today because of that feature.

Clojure was designed for repl driven development, an advantage of which is faster feedback.

REPL development is faster than test driven development. Full stop. It is. It just is. If you like test driven development because of the rapid feedback loop, REPL driven development is a faster feedback loop, because you do not have to write the test part. You can try things out. ~ Stuart Halloway

With the repl, you can easily redefine functions and invoke them without restarting your application. With CIDER, you can evaluate forms and see the results without ever typing at the repl prompt.

The next logical step is getting feedback without needing to manually evaluate code. The above function cider-eval-n-defuns is a small step in that direction.

Example

Take the above cider-eval-n-defuns function and bind it to an easy shortcut like C-c C-a. Then start up CIDER and open a scratch buffer with the following code.

(defn fizzbuzz
  [n]
  )

[(fizzbuzz 3)
 (fizzbuzz 4)
 (fizzbuzz 5)
 (fizzbuzz 15)]

Ignore that the solution might be trivial to you. You may be tempted to write a quick solution then evaluate the tests one by one. Likewise, you may start by writing a partial solution and then evaluate the tests selectively as you go. You might want to immediately write these tests into a deftest and automatically run the tests when saving the file. You may even decide wrap both forms in do and eval that top level form to get instant feedback.

Instead, you can change the function body and evaluate both forms at once by calling cider-eval-n-defuns with no arguments. As you change the function body, you can almost immediately see how your changes affect the result of evaluating the function.

You've re-framed your development process. Instead of determining tests upfront and driving toward passing them, you're creatively exploring new approaches and ideas until you arrive at one that you like.

What next?

I would love to see an Instarepl mode for CIDER.

Given CIDER's roadmap, it's simply not a priority right now. Some day it could be though. My hope is that this gist gives Clojure programmers a taste of how imaginative programming Clojure can be.

@zcaudate
Copy link

zcaudate commented Oct 12, 2020

There's also cider-eval-buffer - Ctrl-X Ctrl-K - on standard bindings.

@cstby
Copy link
Author

cstby commented Oct 15, 2020

And it's a great tool for when you want to evaluate the buffer in its entirety. ;)

@dantheobserver
Copy link

dantheobserver commented Oct 22, 2020

If you use doom, you can use the modification I made

(defun eval-n-defuns (n)
  "Evaluate N top-level forms, starting with the current one."
  (interactive "P")
  (+eval/region (car (bounds-of-thing-at-point 'defun))
                (save-excursion
                  (dotimes (_ (or n 2))
                    (end-of-defun))
                  (point))))

basically the +eval/* functions check to see what the major mode's eval is from a configurable variable, evals and displays an overlay, works with elisp as well

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment