Namespaces in Clojure

Despite several good texts that attempted to explain them (including the outstanding Clojure for the Brave and True), I had until recently a fuzzy understanding of how namespaces (object naming in general, really) work in Clojure. I finally bit the bullet and dug into the surprisingly readable source code for Clojure and sorted things out for myself.

To start at the global scope and drill down: the Clojure runtime maintains a map from symbols to namespaces. I don’t see a way to expose this directly as a map within Clojure itself, but all-ns will iterate over the namespaces. You can then use ns-name to get the symbolic name for a namespace if you need it.

The current namespace is determined by the value of the dynamic Var clojure.core/*ns*.

As it turns out, namespaces are surprisingly simple. A namespace is just a pair of maps: one from symbols to Vars and classes (the namespace map, returned by ns-map), and one from symbols to namespaces (the alias map, returned by ns-aliases). The namespace map is what refers to the objects contained in the namespace; e.g., if you enter foo/bar (or just bar if foo is your current namespace) in your code or at the REPL, the evaluator looks in foo‘s namespace map for a Var or class associated with the symbol bar. The alias map provides aliases for one or more namespaces within another namespace. E.g., if namespace foo has an entry in its alias map from the symbol b to the namespace baz and foo is the current namespace, b/bar will be equivalent to baz/bar.

One detail that I hadn’t been clear on before: Vars contain a reference to the namespace in which they are interned, as well as the symbol with which they are initially associated. Namespaces themselves do not directly track which Vars are interned in them; if you call ns-interns, it just filters the Var values in the namespace map to only include those that claim the namespace in question as the one in which they are interned. The symbol that the Var gets when it is created is as far as I can tell only used for documentation, not identification. If foo is interned into namespace ns1, referred into namespace ns2 as bar, and then removed from ns1 using ns-unmap, the Var returned will still call itself #'ns1/foo, even though it is only accessible as ns2/bar.

Leave a Reply