Two Ways of Defining Private Functions in Clojure
Introduction
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
.
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 ...)
Then:
(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 havedef-
,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.