Bill Clementson's Blog

Bits and pieces (mostly Lisp-related) that I collect from the ether.

October 2008
Sun Mon Tue Wed Thu Fri Sat
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Sep  Nov

Exploring Clojure (Lisp on the JVM) - Part 4: Not Your Daddy's Namespaces

Wednesday, October 29, 2008

Ok, now I know that Namespaces are probably not the sexiest topic for a blog post and, since you're reading this at all, you are probably just curious as to why someone would possibly want to blog about them! Well, when I first started looking at namespaces in Clojure, I probably would have said the same thing. However, as I played around with them a bit, I found that namespaces provided some interesting info about how Clojure works "under the covers". So, in this post, I won't try to cover all of the namespace functions (that's easy enough to read up on), I'll just try to go through the "interesting bits" that I discovered while playing.

First of all, Clojure exposes 6 different namespaces through it's API:

  1. clojure: All of the basic set of Clojure functionality.
  2. clojure.inspector: The Clojure graphical (Swing) inspector.
  3. clojure.parallel: The parallel library wraps the ForkJoin (JSR 166) library for parallel processing.
  4. clojure.set: Set-based processing functionality.
  5. clojure.xml: Simple XML processing support.
  6. clojure.zip: Functional hierarchical zipper, with navigation, editing and enumeration. (see Huet)
When you start up a REPL, the "user" namespace is created. It "refer"s to the "clojure" namespace, so it contains "clojure" mappings on startup. It should be noted that Clojure's Special Forms are always available and are not part of any specific namespace. So, for example, "concat" shows up as being present in a namespace but "do" (a special form) does not:
user> (resolve 'concat)
#=(var clojure/concat)
user> (resolve 'do)
nil
Therefore, if you re-define something that is a special form, you have to specify the namespace as well when using the new definition:
user> (defn do [] (+ 1 2))
#=(var user/do)
user> (do)
nil
user> (user/do)
3
When you create a new namespace, it contains mappings for the classnames in java.lang, and nothing else (e.g. - there are no vars yet interned in the namespace):
user> (create-ns 'test)
#=(find-ns test)
user> (count (ns-map 'test))
96
user> (count (ns-interns 'test))
0
user> (doseq x (ns-map 'test) (printf "%s: %s\n" (first x) (frest x)))
ProcessBuilder: class java.lang.ProcessBuilder
Enum: class java.lang.Enum
SuppressWarnings: interface java.lang.SuppressWarnings
Throwable: class java.lang.Throwable
[snipped output]
So, regardless of the namespace that you're in, you're always guaranteed to have certain default Java classes/interfaces available in the namespace. When you create your own vars in the namespace, the names are interned in the namespace (with mappings as well):
user> (in-ns 'test)
#=(find-ns test)
test> (clojure/defn three [] 3)
#=(var test/three)
test> (in-ns 'user)
#=(find-ns user)
user> (count (ns-map 'test))
97
user> (count (ns-interns 'test))
1
It's interesting to compare the default mappings with the ones that are provide in the "clojure" namespace. So, first, remove the example binding that we did previously in the "test" namespace and create the sequence XOR function that I discussed in my previous post:
user> (ns-unmap 'test 'three)
nil
user> (defn seq-xor [& seqs]
  (seq (second
	(reduce (fn [[all ret] x]
		  (if (contains? all x)
		    [all (disj ret x)]
		    [(conj all x) (conj ret x)]))
		[#{} #{}] (mapcat distinct seqs)))))
#=(var user/seq-xor)
Now, list the mappings that are in clojure but not in the new namespace:
user> (doseq x (seq-xor (ns-map 'test) (seq-xor (ns-interns 'clojure) (ns-map 'clojure))) (printf "%s: %s\n" (first x) (frest x)))
ClassVisitor: interface clojure.asm.ClassVisitor
Modifier: class java.lang.reflect.Modifier
DynamicClassLoader: class clojure.lang.DynamicClassLoader
Reflector: class clojure.lang.Reflector
BlockingQueue: interface java.util.concurrent.BlockingQueue
Constructor: class java.lang.reflect.Constructor
Method: class clojure.asm.commons.Method
IProxy: interface clojure.lang.IProxy
PersistentHashMap: class clojure.lang.PersistentHashMap
RT: class clojure.lang.RT
Opcodes: interface clojure.asm.Opcodes
Type: class clojure.asm.Type
Writer: class java.io.Writer
LinkedBlockingQueue: class java.util.concurrent.LinkedBlockingQueue
IPersistentMap: interface clojure.lang.IPersistentMap
ClassWriter: class clojure.asm.ClassWriter
GeneratorAdapter: class clojure.asm.commons.GeneratorAdapter
Array: class java.lang.reflect.Array
Any Java class/interface that is listed in the namespace's mapping is available for use via Clojure's Java Interop support. So, for example, since "Thread" is included by default in the namespace, we can pause the REPL for 1000 ms by doing:
(. Thread (sleep 1000))
And, to add a new Java mapping, one just uses Clojure's "import" function:
user> (in-ns 'test)
#=(find-ns test)
test> (clojure/import '(javax.swing JFrame))
nil
test> (in-ns 'user)
#=(find-ns user)
user> (ns-resolve 'test 'JFrame)
#=javax.swing.JFrame
So, namespaces in Clojure provide a mechanism for mapping not just Clojure var names but Java names too! Quite an interesting approach since it helps to make Java Interop so "natural" in Clojure. Namespaces in Clojure are definitely not your daddy's namespaces! ;-)

Update-2008-10-29: On the Clojure mailing list, Stuart Halloway pointed out the "ns" macro:
"You might also mention that when you actually switch to a namespace using the ns macro, clojure gets referred, giving you a bunch more stuff:"
user=>  (create-ns 'test) 
#=(find-ns test) 
user=> (count (ns-map 'test)) 
96 
user=> (ns test) 
nil 
test=> (ns user) 
nil 
user=> (count (ns-map 'test)) 
513 
Looking at the doc string for the macro, it looks like "ns" provides a bunch of "convenience" functionality for when you're switching to a namespace:
"Sets *ns* to the namespace named by name (unevaluated), creating it if needed. references can be zero or more of: (:refer-clojure ...) (:require ...) (:use ...) (:import ...) (:load ...) with the syntax of refer-clojure/require/use/import/load respectively, except the arguments are unevaluated and need not be quoted. If :refer-clojure is not used, a default (refer 'clojure) is used. Use of ns is preferred to individual calls to in-ns/require/use/import:"
(ns foo 
    (:refer-clojure :exclude [ancestors printf]) 
    (:require (clojure.contrib sql sql.tests)) 
    (:use (my.lib this that)) 
    (:import (java.util Date Timer Random) 
              (java.sql Connection Statement)) 
    (:load "/mystuff/foo.clj"))


Rich Hickey also provided the following clarifications:
"A quick couple of things:

Your post refers to symbols in a few places where it should say vars. Unlike CL, symbols are just names, with no associated values. The things that are more like symbols from that perspective in Clojure are vars, and it is vars that are interned in namespaces. Two symbols with the same name can be distinct objects. A namespace is not a set of symbols but a set of mappings from symbols to references - either vars or classes.

This separation of concerns is an important part of how Clojure is a Lisp-1 while still supporting defmacro semi-hygienically. The reader reads plain symbols and does no interning. The compiler resolves names (symbols) in the compilation namespace in order to find vars/classes. def interns new vars, and import/refer/use can make new mappings.

ns is the preferred way to define/setup a namespace (think defpackage) - it is more declarative. But it is intended to be used once only. Using in-ns as you did is the right way to change namespaces at the repl - ns is not for that.

I can explain more later, but wanted to clarify these points a bit."
I've updated my blog post to say "var" instead of "symbol".

emacs Copyright © 2008 by Bill Clementson