Saturday, 04 February 2017

Namespace-qualified keywords have existed since the beginning of Clojure, but they have seen relatively little use.
However, Clojure 1.9 will introduce spec, and spec uses this feature quite heavily.
Now that this once-obscure feature is getting some real attention, it has led to a lot of confusion.
Do you know the difference between :foo, ::foo, ::bar/foo, and :bar/foo?
If not, I hope you will by the end of this post.

First, a refresher on namespace-qualified symbols

Keywords, like symbols, can be qualified with a namespace.
As a Clojure developer, you probably have some experience with namespace-qualified symbols.
For example, you may have used something like:

On line 2 of this example, we require the namespace clojure.string and alias it to str.
As a result, on line 6, Clojure will see str/join and resolve the namespace alias and interpret the symbol as clojure.string/join.

Note that Clojure treats the str on its own differently than the str before the slash in str/join.
The str by itself is looked up within the scope of the current namespace, where it has been referred to clojure.core/str (you can see all referred symbols by running (ns-refers *ns*).
In contrast, str/join is a namespace-qualified symbol, so Clojure looks for a join in the str namespace, which is aliased to clojure.string.

However, you do not have to use aliases, you can write the following equivalent code:

In line 4, we see that omitting the namespace gets us what we are used to: :foo evaluates to :foo.
However, as we see in lines 5–7, we can add a namespace, which results in having a keyword with that namespace, regardless of whether that namespace exists or not—the keywords merely evaluate to themselves.
However, note that on line 8 :str/foo does not evaluate to :clojure.string/foo.
If we want that feature, we need use a double-colon keyword.

Double-colon keywords: keywords with namespace resolution

Keywords that start with two colons instead of just one participate in namespace resolution, much like symbols:

Lines 4 and 5 above produce the same output.
In the case of line 4, Clojure finds that str is an alias to clojure.string, so the result is the namespace-qualified keyword :clojure.string/foo (note there is only a single colon).
On line 5, clojure.string itself is a namespace, so no additional resolution is needed and the result is :clojure.string/foo (again, one colon).
Note that lines 6 and 7 both produce Invalid token errors because neither awesome or clojure.is.awesome are valid namespace names or aliases.

One last thing about double-colon keywords: when you use them without specifying a namespace, they evaluate to a namespace-qualified keyword with the current namespace: