Two Ways of Defining Private Functions in Clojure

Inconsistent privacy


Clojure provides two ways of defining private functions: (defn ^:private ...) and (defn- ...). Which should we use?

This post looks into ways of defining public vars, ways of defining private vars, and an inconsistency between the two.

Defining Public Vars    

We can define a public var using def and a public function using defn.

Strictly speaking, defn defines a var that contains a function. In this post we want to focus on public and private vars, and we will allow ourselves a little leeway with our terminology for vars and functions.

For example:

(ns my-ns)

(def x1 ...)

(defn f1 [...] ...)

With the above definitions, we can refer to x1 and f1 from other namespaces using the ns/name notation:

(ns my-other-ns
  (:require my-ns))

... my-ns/x1 ...
... my-ns/f1 ...

Defining Private Vars    

We can define a private function using defn-:

(ns my-ns)

(defn- f2 [...] ...)

With this, the var f2 is private and cannot be accessed from other namespaces using my-ns/f2:

(ns my-other-ns
  (:require my-ns))

... my-ns/f2 ... ; Syntax error: f2 is not public.

Surprisingly, perhaps, the defn operator is the only one that has a special version for defining private vars — so for example, there is no def-, no defonce-, and no defmulti-.

But all is not lost: if we want to define a private var we can add :private metadata as follows:

(ns my-ns)

(def ^:private x2 ...)


(ns my-other-ns
  (:require my-ns))

... my-ns/x2 ... ; Syntax error: x2 is not public.

This approach is completely general, and works with any defining operator. It even works with defn:

(ns my-ns)

(defn ^:private f2 [...] ...) ; Same as `defn-`.

Note that (defn ^:private ...) is exactly equivalent to (defn- ...).

Recap and Some Questions    

We have seen that:

  • We can use :private metadata to define private vars; this approach is completely general and can be used with any defining operator.

  • For private functions, we have the option of using the defn- operator.

This prompts some questions:

  • Why aren't things more consistent? (Why does Clojure have defn- as a special case? Or the other way around: why doesn't Clojure have def-, defonce-, defmulti-, etc?)

  • Which of (defn ^:private ...) and (defn- ...) should we use?

Some Answers    

Why Things are Inconsistent    

I'm not aware of any justification ever having been given for the lack of consistency. However, the core Clojure team have said that they will not be adding other "-" operators, so we should probably view the inconsistency as an accident of history.

Which of (defn ^:private ...) and (defn- ...) to Use    

So, which of (defn ^:private ...) and (defn- ...) should we use?

The argument for (defn ^:private ...) is that it is consistent with other defining operators, and the argument for (defn- ...) is that it is more concise.

I prefer to use (defn ^:private ...) for two reasons:

  • I like the consistency: When skimming code I just have to look for one kind of thing if I want to see what's private. Also, I can position my cursor on a ^:private annotation and my editor will highlight all other occurrences of ^:private.

  • I think it's easier to read: I find the "-" in defn- easy to miss.

Closing Thoughts    

Ultimately, the choice between (defn ^:private ...) and (defn- ...) is a matter of taste. I've presented my reasons for preferring (defn ^:private ...) and I hope that at least some people will find my reasoning convincing. But I grudgingly accept that others may see things differently.