When should clojure keywords be in namespaces?
In c开发者_如何学编程lojure, keywords evaluate to themselves, e.g.:
>>:test
:test
They don't take any parameters, and they aren't bound to anything. Why then, would we need to qualify keywords in a namespace?
I know that creating isa
hierachies using derive
requires namespace qualified keywords (e.g. ::test
). Are there any other cases where there is a clear need for keywords to be in a namespace?
You should namespace-qualify your keywords if any code is ever going to have a chance to interact with your keywords outside of the context of your namespace. The main example I can think of is two namespaces putting keys and values into a hash-map in a third namespace, where the keys are keywords (as they often are in Clojure). A contrived example:
user> (ns main)
nil
main> (def DATA (ref {}))
#'main/DATA
main> (ns foo)
nil
foo> (dosync (alter main/DATA assoc :bad 123 ::good 123))
{:foo/good 123, :bad 123}
foo> main/DATA
#<Ref@541b02: {:foo/good 123, :bad 123}>
foo> (ns bar)
nil
bar> (dosync (alter main/DATA assoc :bad 456 ::good 456))
{:bar/good 456, :foo/good 123, :bad 456} ;; oops, no more :bad from foo
Why would you want to do this? Well, some core concepts in Clojure (like derive
, for example) are implemented this way, as hash-maps in clojure.core
. Multimethods also often dispatch on keyword values, and it's not uncommon for a namespace to define a method for a multimethod in another namespace. It's not hard to think of situations where the author of a library might like to provide a similar sort of mechanism.
It's a good idea to namespace-qualify your keywords if there's any risk of your keywords escaping your namespace, unless you specifically want your keywords to clobber those from other namespaces.
One place where Clojure currently requires namespace-qualified keywords is when using the extend
function function to add an implementation of a protocol to an existing type. (This is 1.2 functionality, available with the latest snapshots, but not the 1.1 stable release.) The relevant snippet from (doc extend)
:
extend takes a type/class (or interface, see below), and one or more protocol + method map pairs. It will extend the polymorphism of the protocol's methods to call the supplied methods when an AType is provided as the first argument. Note that deftype types are specified using their keyword tags:
::MyType or :my.ns/MyType
Indeed, for an Apple type and an Eatable protocol:
(deftype Apple [colour])
(defprotocol Eatable (eat [x]))
the following throws an exception (No implementation of method: :eat
etc.):
(extend :Apple Eatable {:eat (fn [x] (println (str (name (:colour x)) ", yummy!")))})
(eat (Apple :red))
while this prints out red, yummy!
:
(extend ::Apple Eatable {:eat (fn [x] (println (str (name (:colour x)) ", yummy!")))})
(eat (Apple :red))
Note I've just typed it all in at the REPL. Also note that if you want to reproduce it, it's best that you type / paste it in in the order given above; e.g. reevaluating either of the (deftype Apple [colour])
and (defprotocol Eatable (eat [x]))
forms (or even both) does not make Clojure forget about the protocol implementation.
Again, this is a 1.2 feature, so it's not even there in 1.1 and might change prior to the actual release of 1.2.
Sharing a hash-map between a number of namespaces is another possible use case, as Brian says. Note that there's no need for a reference type to be involved. Say there's a bunch of libraries -- which may perhaps be added to in the future -- which manipulate (maybe transform, maybe just observe) hash-maps keyed by keywords, where each library is free to define which keywords it looks for and what it uses them for; then one might be tempted to use namespace qualified keywords to avoid collisions.
Actually, there currently exists an example which is not at all contrived, namely the Ring spec v0.1 (a key piece of the present day Clojure Web ecosystem). See the Request and response map keyword namespacing message to the Ring Google group (made by Ring author
Mark McGranaghan) for some insight into the rationale behind that design decision, as well as behind the decision to no longer require namespacing keywords in Ring spec v0.2. There's also a message by James Reeves (the author of Compojure) in support of the change.
Ultimately, as all namespacing, this is a collision avoidance feature. Clojure code that is written nowadays doesn't tend to care about having "private" keywords, so they don't see much use; but it is good to have them available for when they may make a difference.
精彩评论