Search⌘ K
AI Features

Clojure Conventions

Explore the key conventions in Clojure coding that support writing clean, maintainable, and extensible programs. Understand proper naming patterns, spacing, indentation, line usage, and namespace management to ensure your code adheres to functional programming standards and best practices.

We'll cover the following...

Like most languages, Clojure has conventions and best practices for the layout and organization of source code. Let’s take a look at these below.

Names

As you might have noticed, Clojure uses the kebab-case to define most of the names in Clojure, which means that the names are all in lowercase and separated by the dash symbol (-). We’ll define def, defn, bindings in general, and many others with this style.

Clojure
(def my-variable-with-many-information 10)

Use PascalCase for protocols, records, structs, and types.

Clojure
(defprotocol Dog
(bark [dog] "A function to represent the sound of the bark")
(eat [dog] "A function representing the action of eating")
(sleep [dog] "A function representing the action of sleeping"))
(defrecord Labrador []
Dog
(bark [dog] (println "Woof, woof."))
(eat [dog] (println "I WANT IT ALL"))
(sleep [dog] (println "SLEEP 1 . 2 . 3")))

The names of predicate functions, which return a boolean value, should end in a question mark (?). In this way, we always know that that function is a predicate. A Clojure example is the even? function.

Clojure
(defn valid-numbers?
[a b]
(= a b))

The names of functions that might generate a side effect should end with an exclamation point (!). Examples of these are functions that interact with databases, call an HTTP request, or do anything else that’s considered a side effect. A Clojure example is the reset! function.

Clojure
(def update-produc-status!
[product database-component]
; Doing an update to the database
)

Use -> instead of to in the names of conversion functions.

Clojure
; Best approach
(defn a-model->b-model ...)
; Not so good
(defn a-model-to-b-model ...)

Spaces

Use spaces for indentation instead of hard tabs, which use the tab character.

Use two spaces to indent internal forms that have body parameters, like def, defn, special forms, and macros let, when-let, when, cond, as->, cond->, case, with-*, etc.

Clojure
; Correct examples
(when condition
(action-happens))
(with-out-str
(println "Hello")
(println "world"))
; Wrong - four spaces
(when condition
(action-happens))
; Wrong - one space
(with-out-str
(println "Hello")
(println "world"))

Function arguments should be vertically aligned when spanning multiple lines.

Clojure
; Correct
(filter even?
(range 1 10))
; Wrong
(filter even?
(range 1 10))

Vertically align let bindings and hashMap keywords.

Clojure
; Correct
(let [my-binding-1 "Hello"
binding-2 "World"]
{:my-hello my-binding-1
:world binding-2})
; Wrong
(let [my-binding-1 "Hello"
binding-2 "World"]
{:my-hello my-binding-1
:world binding-2})

There should be no spaces between brackets, but outside the brackets, there should be a space before more content is added.

Clojure
; Correct
(foo (bar baz) quux)
; Wrong
(foo(bar baz)quux)
(foo ( bar baz ) quux )

Lines

The position of the argument when defining a function with defn can be at the same line or in the line below.

Clojure
; The correct forms:
; One in each line
(defn foo
[x]
(bar x))
; defn and argument on the same line
(defn foo [x]
(bar x))
; With documentation, one in each line
(defn foo
"docstring"
[x]
(bar x))
; Wrong
; We shouldn't leave the implementation of the function in the same line as the argument
(defn foo
[x] (bar x))
; When we have documentation, we can’t do it like this.
; The documentation string should be just after the function name.
; This way is incorrect and will fail.
(defn foo [x]
"docstring"
(bar x))

The only exception to same-line implementation and arguments goes to the defmethod function.

Clojure
; Both correct ways:
(defmethod foo :bar [x] (baz x))
(defmethod foo :bar
[x]
(baz x))
; Wrong ways:
(defmethod foo
:bar
[x]
(baz x))
(defmethod foo
:bar [x]
(baz x))

All trailing parentheses should be put in a single line instead of distinct lines, as we sometimes see in other languages.

Clojure
; Correct: Single line
(when condition
(action-happens))
; Wrong: Distinct lines
(when condition
(action-happens)
)

Commas

Don’t use commas between the elements of sequential collection literals.

Clojure
; Correct
[1 2 3]
(1 2 3)
; Wrong: This won't fail, but we never use commas in Clojure
[1, 2, 3]
(1, 2, 3)

Namespaces

The namespace of a file should consist of the whole path from the source to the file name.

Clojure
(ns com.example.my-file-hello)

When referring to other namespaces, keep a consistent naming to the alias. Don’t use different aliases for the same namespaces within a service. As a general first rule, make the alias the same as the namespace name with the leading parts removed.

Clojure
(ns com.example.my-file-hello
(:require [schema.core :as s]
[ring.core.protocols :as core-protocols]))

In the namespace, it’s preferable to use :require with namespaces with the :as alias instead of using :refer to a specific function. Using :refer can lead to confusion while reading of the code because it will look like the function belongs to the same namespace. Also, avoid using :require :refer :all, which is more problematic than only referring to specific functions. In this case, we won’t be able to determine where the function originates from.

Clojure
; Best usage
(ns com.example.my-file-hello
(:require [clojure.zip :as zip]))
; Use with concern
(ns com.example.my-file-hello
(:require [clojure.zip :refer [lefts rights]]))
; Avoid this
(ns com.example.my-file-hello
(:require [clojure.zip :refer :all]))

Syntax

Don’t define variables inside functions; use let instead.

Clojure
; Avoid doing this
(defn foo []
(def x 5)
...)
; Prefer using a let
(defn foo []
(let [x 5]
...))

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

Clojure
; Correct
(when pred
(foo)
(bar))
; Wrong
(if pred
(do
(foo)
(bar)))

Use if-let instead of let + if if we have a single variable. The same principle applies to the other variations of conditionals, when-let, if-not, when-not, and so on.

Clojure
; Better approach because it has fewer lines of code
(if-let [a (foo x)]
(action-with-a a)
(other-action))
; Avoid doing this if it is a single variable
(let [a (foo x)]
(if a
(action-with-a a)
(other-action)))
; Exception - when we have to declare more variables, it is ok to do it
(let [a (foo x)
b (foo2 y)
c (foo3 w)]
(action-wit-b-and-c b c)
(if a
(action-with-a a)
(other-action)))

Don’t wrap functions in anonymous functions if it’s not needed, especially in transformation functions, such as filter, map, reduce, and similar functions that can directly accept anonymous functions.

Clojure
; Correct
(filter even? (range 1 10))
; Wrong
(filter #(even? %) (range 1 10))

It’s preferable to use threading macros, thread-first (->) and thread-last (->>), to avoid excessive nesting of forms.

Clojure
; Best readable form of thread first
(-> [1 2 3 4 5 6]
reverse
(conj 7)
println)
; Less readable as the flow is not clear
(println (conj (reverse [1 2 3 4 5 6]) 7))
; Best readable form of thread last
(->> (range 1 10)
(filter even?)
(map (partial * 2)))
; Less readable as the flow is not as clear
(map (partial * 2) (filter even? (range 1 10)))

Keeping these points in mind, we’ll be able to develop and validate if a Clojure code is well written or not.