Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When should clojure keywords be in namespaces?

In clojure, 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?

like image 794
Rob Lachlan Avatar asked Mar 20 '10 04:03

Rob Lachlan


People also ask

What is a Clojure keyword?

Keywords are symbolic identifiers that evaluate to themselves. They provide very fast equality tests... Symbols are identifiers that are normally used to refer to something else. They can be used in program forms to refer to function parameters, let bindings, class names and global vars...

What is a Clojure namespace?

A namespace is both a name context and a container for vars. Namespace names are symbols where periods are used to separate namespace parts, such as clojure. string . By convention, namespace names are typically lower-case and use - to separate words, although this is not required.


2 Answers

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.

like image 104
Brian Carper Avatar answered Oct 12 '22 12:10

Brian Carper


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.

like image 42
Michał Marczyk Avatar answered Oct 12 '22 14:10

Michał Marczyk