Skip to content

Instantly share code, notes, and snippets.

@technomancy
Last active January 22, 2024 22:45
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save technomancy/1e29d5a61ef8a61252257e8842ff5acc to your computer and use it in GitHub Desktop.
Save technomancy/1e29d5a61ef8a61252257e8842ff5acc to your computer and use it in GitHub Desktop.
Clojure Style Guide: commentary

The Clojure Style Guide is pretty good overall. It's very detailed and most of its advice is solid. There are handful of places it makes bad recommendations or is missing some advice.

Many of these criticisms apply to the output of linters like clj-kondo as well.

threading macros vs let

The style guide says to prefer threading macros in all cases to heavy form nesting. This can lead to code that's not very readable. Most cases where you need to get rid of heavy nesting would be more readable as a let. The advantage of the threading macros is that it lets you avoid choosing names for the intermediate values. In some cases this is helpful, when the names you would have written are obvious or just repeating the same thing, go ahead and use -> instead if you can. But there are cases where the names clarify things, and using -> there would only obscure the code.

When in doubt, write the let version next to the -> version and compare them side by side. Do the additional names in the let version clarify the code or do they get in the way? That should be the deciding factor.

when vs if

The style guide says never to use if when you only have one branch but to use when instead; this is hogwash. The point of when is the implicit do it contains; this means that when can be used to imply side-effects. There are plenty of times you're using a single-branched if where no side-effects are present; using when there would be misleading.

There is no rationale given in the style guide for this rule, because it doesn't make any sense. Making it about side-effects means that the choice between when and if can communicate something meaningful to the reader. Making it about when there's only one branch turns it into a meaningless distinction.

This is a lisp convention that is much older than Clojure. If you find that Elisp programmers are more concerned with separating side-effects from values than you are, you should probably reconsider your position.

:refer :all

The style guide says to avoid :refer :all. This is good advice 99% of the time. However, you should use :refer :all when writing tests and bringing in the clojure.test namespace. That one case is the only reason :refer :all was even implemented.

I hate to resort to this, but I'm the one who implemented this functionality, so I think that means my opinion carries more weight than yours here.

seq over not+empty?

You should focus on making code more readable, and empty? is more readable than seq. If you can swap the branches of an if to avoid the double-negative of not+empty? that's great, but ignore the advice to use seq as a predicate. Using not-empty as a predicate works great too.

sorting requires

Look, if you want to sort your requires, that's fine. But that doesn't mean everyone should do it. The worst thing is when you encounter a mostly-sorted list of require clauses and you look for a specific ns and it doesn't appear to be there, but that's just because you thought it was sorted and that one ns was out of order.

Don't assume require clauses are sorted.

() vs [] for :import

Clojure allows you to use square brackets when importing Java classes. This is a mistake because it results in bad indentation. For example:

  ;; good
  (:import (java.io File
                    FileOutputStream
                    PrintWriter
                    InputStream))

  ;; bad
  (:import [java.io File
            FileOutputStream
            PrintWriter
            InputStream])

The rationale is that Clojure indentation treats lists differently from vectors; in a vector all the elements are assumed to be roughly "peers" to each other, and of equal importance, but in a list, the first element is interpreted as being special, and all the subsequent elements are lined up under the second element. This is desirable in imports because the first element is the package, and the remaining elements are classes, so you want the classes to be lined up together.

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