Skip to content

Clojure Design Idiomsλ︎

An idiom is the Clojure way

Idioms are not Design Patterns found in languages like C++ and Java. Design patterns have a very specific language and structure and carefully design the context in which they are used.

Idioms are very simple in comparision, they are the way Clojure should be used.

Idiomatic Clojure is a term commonly used, commonly as a question. Is this idiomatic Clojure?

Idioms from style guideλ︎

A (WIP) refactor of the idioms from the Clojure Style guide

Dynamic Namespace Manipulationλ︎

TODO: vauge statement with no example

Avoid the use of namespace-manipulating functions like require and refer. They are entirely unnecessary outside of a REPL environment.

Forward Referencesλ︎

TODO: vauge statement with no example

Avoid forward references. They are occasionally necessary, but such occasions are rare in practice.

Declareλ︎

Use declare to enable forward references when forward references are necessary.

Higher-order Functionsλ︎

TODO: expand and provide examples

Prefer higher-order functions like map to loop/recur.

Appropriate Function useλ︎

  • let def
  • if when cond condp
  • Nil Punning (a pun that null is not a value, when it is??) seq empty?

Use let for local namesλ︎

let should be used inside a function definition or to create a local name when experimenting in the REPL.

def defines a symbol name at the scope of the current namespace and therefore is inappropriate to use within a function definition.

Avoid vars inside functions

(defn iterative-function
  "Avoid breaking the scope of data in a function by using def"
  [args]
    (def x 5)  ; Avoid using a var as it breaks the scope of the function
  ...)

x ; evaluating will return the result without going through the function call and could be used anywere the namespace is required, increasing complexity.

Problematic debuging technique

An obscure debug technique is to define a def inside a function definition to capture intermediate values, however, this breaks the scope of Clojure and can lead to incorrect use of Clojure (especially by those new to the language).

Use a debug tool rather than break the scope of Clojure with a def, e.g. Cider Debug or Flowstorm-debugger.

Avoid Shadowing Namesλ︎

If names for local bindings or function definitions are the same as those from common libraries, confusion and errors is greatly increased. It also makes a code refactor unnecessarily complicated.

Shadowing clojure.core names (or other popular libraries) exaserbated the situation.

Libraries that are highly generic use names. Code for applications or services should be specific and use domain language for greater clarity.

RELATED: avoid renaming functions required by name via the ns definition.

Alter Var Bindingλ︎

Use alter-var-root instead of def to change the value of a var.

[source,clojure]

;; good
(def thing 1) ; value of thing is now 1
; do some stuff with thing
(alter-var-root #'thing (constantly nil)) ; value of thing is now nil

;; bad
(def thing 1)
; do some stuff with thing
(def thing nil)
; value of thing is now nil

Nil Punning [[nil-punning]]λ︎

Use seq as a terminating condition to test whether a sequence is empty (this technique is sometimes called nil punning).

[source,clojure]

;; good
(defn print-seq [s]
  (when (seq s)
    (prn (first s))
    (recur (rest s))))

;; bad
(defn print-seq [s]
  (when-not (empty? s)
    (prn (first s))
    (recur (rest s))))

Converting Sequences to Vectors [[to-vector]]λ︎

Prefer vec over into when you need to convert a sequence into a vector.

[source,clojure]

;; good
(vec some-seq)

;; bad
(into [] some-seq)

Converting Something to Booleanλ︎

Use the boolean function if you need to convert something to an actual boolean value (true or false).

[source,clojure]

;; good
(boolean (foo bar))

;; bad
(if (foo bar) true false)

NOTE: Don't forget that the only values in Clojure that are "falsey" are false and nil. Everything else will evaluate to true when passed to the boolean function.

You'll rarely need an actual boolean value in Clojure, but it's useful to know how to obtain one when you do.

when vs if [[when-instead-of-single-branch-if]]λ︎

Use when instead of if with just the truthy branch, as in (if condition (something...)) or (if ... (do ...)).

[source,clojure]

;; good
(when pred
  (foo)
  (bar))

;; bad
(if pred
  (do
    (foo)
    (bar)))

if-let [[if-let]]λ︎

Use if-let instead of let + if.

[source,clojure]

;; good
(if-let [result (foo x)]
  (something-with result)
  (something-else))

;; bad
(let [result (foo x)]
  (if result
    (something-with result)
    (something-else)))

when-let [[when-let]]λ︎

Use when-let instead of let + when.

[source,clojure]

;; good
(when-let [result (foo x)]
  (do-something-with result)
  (do-something-more-with result))

;; bad
(let [result (foo x)]
  (when result
    (do-something-with result)
    (do-something-more-with result)))

if-not [[if-not]]λ︎

Use if-not instead of (if (not ...) ...).

[source,clojure]

;; good
(if-not pred
  (foo))

;; bad
(if (not pred)
  (foo))

when-not [[when-not]]λ︎

Use when-not instead of (when (not ...) ...).

[source,clojure]

;; good
(when-not pred
  (foo)
  (bar))

;; bad
(when (not pred)
  (foo)
  (bar))

when-not vs if-not [[when-not-instead-of-single-branch-if-not]]λ︎

Use when-not instead of (if-not ... (do ...)).

[source,clojure]

;; good
(when-not pred
  (foo)
  (bar))

;; bad
(if-not pred
  (do
    (foo)
    (bar)))

not= [[not-equal]]λ︎

Use not= instead of (not (= ...)).

[source,clojure]

;; good
(not= foo bar)

;; bad
(not (= foo bar))

printf [[printf]]λ︎

Prefer printf over (print (format ...)).

[source,clojure]

;; good
(printf "Hello, %s!\n" name)

;; ok
(println (format "Hello, %s!" name))

Flexible Comparison Functionsλ︎

When doing comparisons, leverage the fact that Clojure's functions <, >, etc. accept a variable number of arguments.

[source,clojure]

;; good
(< 5 x 10)

;; bad
(and (> x 5) (< x 10))

Single Parameter Function Literal [[single-param-fn-literal]]λ︎

Prefer % over %1 in function literals with only one parameter.

[source,clojure]

;; good
#(Math/round %)

;; bad
#(Math/round %1)

Multiple Parameters Function Literal [[multiple-params-fn-literal]]λ︎

Prefer %1 over % in function literals with more than one parameter.

[source,clojure]

;; good
#(Math/pow %1 %2)

;; bad
#(Math/pow % %2)

No Useless Anonymous Functions [[no-useless-anonymous-fns]]λ︎

Don't wrap functions in anonymous functions when you don't need to.

[source,clojure]

;; good
(filter even? (range 1 10))

;; bad
(filter #(even? %) (range 1 10))

No Multiple Forms in Function Literals [[no-multiple-forms-fn-literals]]λ︎

Don't use function literals if the function body will consist of more than one form.

[source,clojure]

;; good
(fn [x]
  (println x)
  (* x 2))

;; bad (you need an explicit do form)
#(do (println %)
     (* % 2))

Anonymous Functions vs complement, comp and partialλ︎

Prefer anonymous functions over complement, comp and partial, as this results in simpler code most of the time.footnote:[You can read more on the subject https://ask.clojure.org/index.php/8373/when-should-prefer-comp-and-partial-to-anonymous-functions[here].]

= complement [[complement]]λ︎

[source,clojure]

;; good
(filter #(not (some-pred? %)) coll)

;; okish
(filter (complement some-pred?) coll)

= comp [[comp]]λ︎

[source,clojure]

;; Assuming `(:require [clojure.string :as str])`...

;; good
(map #(str/capitalize (str/trim %)) ["top " " test "])

;; okish
(map (comp str/capitalize str/trim) ["top " " test "])

comp is quite useful when composing transducer chains, though.

[source,clojure]

;; good
(def xf
  (comp
    (filter odd?)
    (map inc)
    (take 5)))

= partial [[partial]]λ︎

[source,clojure]

;; good
(map #(+ 5 %) (range 1 10))

;; okish
(map (partial + 5) (range 1 10))

Threading Macros [[threading-macros]]λ︎

Prefer the use of the threading macros +->+ (thread-first) and +->>+ (thread-last) to heavy form nesting.

[source,clojure]

;; good
(-> [1 2 3]
    reverse
    (conj 4)
    prn)

;; not as good
(prn (conj (reverse [1 2 3])
           4))

;; good
(->> (range 1 10)
     (filter even?)
     (map (partial * 2)))

;; not as good
(map (partial * 2)
     (filter even? (range 1 10)))

Threading Macros and Optional Parenthesesλ︎

Parentheses are not required when using the threading macros for functions having no argument specified, so use them only when necessary.

[source,clojure]

;; good
(-> x fizz :foo first frob)

;; bad; parens add clutter and are not needed
(-> x (fizz) (:foo) (first) (frob))

;; good, parens are necessary with an arg
(-> x
    (fizz a b)
    :foo
    first
    (frob x y))

Threading Macros Alignmentλ︎

The arguments to the threading macros +->+ (thread-first) and +->>+ (thread-last) should line up.

[source,clojure]

;; good
(->> (range)
     (filter even?)
     (take 5))

;; bad
(->> (range)
  (filter even?)
  (take 5))

Default cond Branch [[else-keyword-in-cond]]λ︎

Use :else as the catch-all test expression in cond.

[source,clojure]

;; good
(cond
  (neg? n) "negative"
  (pos? n) "positive"
  :else "zero")

;; bad
(cond
  (neg? n) "negative"
  (pos? n) "positive"
  true "zero")

condp vs cond [[condp]]λ︎

Prefer condp instead of cond when the predicate & expression don't change.

[source,clojure]

;; good
(cond
  (= x 10) :ten
  (= x 20) :twenty
  (= x 30) :thirty
  :else :dunno)

;; much better
(condp = x
  10 :ten
  20 :twenty
  30 :thirty
  :dunno)

case vs cond/condp [[case]]λ︎

Prefer case instead of cond or condp when test expressions are compile-time constants.

[source,clojure]

;; good
(cond
  (= x 10) :ten
  (= x 20) :twenty
  (= x 30) :forty
  :else :dunno)

;; better
(condp = x
  10 :ten
  20 :twenty
  30 :forty
  :dunno)

;; best
(case x
  10 :ten
  20 :twenty
  30 :forty
  :dunno)

Short Forms In Cond [[short-forms-in-cond]]λ︎

Use short forms in cond and related. If not possible give visual hints for the pairwise grouping with comments or empty lines.

[source,clojure]

;; good
(cond
  (test1) (action1)
  (test2) (action2)
  :else   (default-action))

;; ok-ish
(cond
  ;; test case 1
  (test1)
  (long-function-name-which-requires-a-new-line
    (complicated-sub-form
      (-> 'which-spans multiple-lines)))

  ;; test case 2
  (test2)
  (another-very-long-function-name
    (yet-another-sub-form
      (-> 'which-spans multiple-lines)))

  :else
  (the-fall-through-default-case
    (which-also-spans 'multiple
                      'lines)))

Set As Predicate [[set-as-predicate]]λ︎

Use a set as a predicate when appropriate.

[source,clojure]

;; good
(remove #{1} [0 1 2 3 4 5])

;; bad
(remove #(= % 1) [0 1 2 3 4 5])

;; good
(count (filter #{\a \e \i \o \u} "mary had a little lamb"))

;; bad
(count (filter #(or (= % \a)
                    (= % \e)
                    (= % \i)
                    (= % \o)
                    (= % \u))
               "mary had a little lamb"))

inc and dec [[inc-and-dec]]λ︎

Use (inc x) & (dec x) instead of (+ x 1) and (- x 1).

pos? and neg? [[pos-and-neg]]λ︎

Use (pos? x), (neg? x) & (zero? x) instead of (> x 0), (< x 0) & (= x 0).

list* vs cons [[list-star-instead-of-nested-cons]]λ︎

Use list* instead of a series of nested cons invocations.

[source,clojure]

;; good
(list* 1 2 3 [4 5])

;; bad
(cons 1 (cons 2 (cons 3 [4 5])))

Sugared Java Interop [[sugared-java-interop]]λ︎

Use the sugared Java interop forms.

;;; object creation
;; good
(java.util.ArrayList. 100)

;; bad
(new java.util.ArrayList 100)

;;; static method invocation
;; good
(Math/pow 2 10)

;; bad
(. Math pow 2 10)

;;; instance method invocation
;; good
(.substring "hello" 1 3)

;; bad
(. "hello" substring 1 3)

;;; static field access
;; good
Integer/MAX_VALUE

;; bad
(. Integer MAX_VALUE)

;;; instance field access
;; good
(.someField some-object)

;; bad
(. some-object someField)

Compact Metadata Notation For True Flags [[compact-metadata-notation-for-true-flags]]λ︎

Use the compact metadata notation for metadata that contains only slots whose keys are keywords and whose value is boolean true.

[source,clojure]

;; good
(def ^:private a 5)

;; bad
(def ^{:private true} a 5)

Privateλ︎

Denote private parts of your code.

[source,clojure]

;; good
(defn- private-fun [] ...)

(def ^:private private-var ...)

;; bad
(defn private-fun [] ...) ; not private at all

(defn ^:private private-fun [] ...) ; overly verbose

(def private-var ...) ; not private at all

Access Private Var [[access-private-var]]λ︎

To access a private var (e.g. for testing), use the @#'some.ns/var form.

Attach Metadata Carefully [[attach-metadata-carefully]]λ︎

Be careful regarding what exactly you attach metadata to.

[source,clojure]

;; we attach the metadata to the var referenced by `a`
(def ^:private a {})
(meta a) ;=> nil
(meta #'a) ;=> {:private true}

;; we attach the metadata to the empty hash-map value
(def a ^:private {})
(meta a) ;=> {:private true}
(meta #'a) ;=> nil

Namingλ︎

Using meaningful names in kebab-case for all symbols

  • namespaces / names / vars should be concise but not terse
  • fully qualified keywords
  • naming is hard to do well... k m v

Namespacesλ︎

Use meaningful alias names in require expressions.

Only use refer if the purpose of the current namespace is central the specific functions being referred, e.g. clojure.test

Avoid the use function as it it inters all funcions and can lead to many conflics in function names and other vars.

Documentationλ︎

Function definitions and shared values should include doc-strings that describe the purpose of the function. Arguments and return values should be included in the doc-string

clj-kondo - code quality and idiom checkλ︎

clj-kondo provides live linting and idiom checker within the editor and is also run as a process during continuous integration checks.

Code Analysis clj-kondo GitHub action runs a limited set of lint rules for clj-kondo

  • Idiom section of a community style guide

clj-format - deprecated - check code format with rules (quite restrictive, not universally used) - currently used on master, removed in spike/refactor-thinking

kibit - deprecated - the classic Clojure idiom checker - currently used on master, removed in spike/refactor-thinking

Meta dataλ︎

  • var scope if required (def, defn) - ^:private
  • test selectors - ^:integration , ^:datastore
  • depreciated namespaces and functions - ^:deprecated

Macrosλ︎

First rule of macro club... don't write your own macros 😂

Defining your own macros is the path of language design, which has different concerns to application and service design.

If there is no way to satisfy a requirement with function definitions and other vars, then define a macro wisely and ensure the macro has enough worth to merit inclusion into multiple projects.

Coding Styleλ︎

Optimise for readability and consistency

  • Long functions / long lines
  • Aligning forms
  • Avoid the use of declare - refactor code to be less inter-dependent
  • Whitespace commit white space /formatting changes separately from application code changes where possible
  • ;; for line comments

NOTE: Cursive setting: Editor > General > Smart Keys > Clojure > Use ;; for line comments

Refactor as you goλ︎

  • Review code after tests work for opportunity to refactor
  • minimise local name bindings
  • consider how understandable code for humans
  • ensure there is a doc-string & update if required

Removing redundant codeλ︎

Less code tends to fewer bugs and easier maintenance

  • check references to functions
  • check for similar functions
  • mark code as deprecated if its use is not clear (using a comment or ^deprecated)
  • Carve tool to highlight unused vars - example using practicalli/clojure-deps-edn config -

Carveλ︎

Carve will report and remove vars (defn, def, requires) that are in the code but not used.

clojure -M:project/unused --opts '{:paths ["src" "test"]} :report {:format :text}}' > carve-report.txt