Chapter 7. Namespaces and Libraries

Organizing Clojure Code

Namespaces are the means by which you divide your Clojure code into logical groups, similar to packages in Java or modules in other languages. Almost every Clojure source file begins with a namespace declaration using the ns macro. The following code is an example of a namespace declaration:

(ns clojure.contrib.gen-html-docs
  (:require [clojure.contrib.duck-streams :as duck-streams])
  (:use (clojure.contrib seq-utils str-utils repl-utils def prxml))
  (:import (java.lang Exception)
            (java.util.regex Pattern)))

Fundamentally, a namespace is just a Clojure map. The keys of the map are Clojure symbols and the values are either Clojure Vars or Java classes. The Clojure compiler uses that map to figure out the meaning of each symbol in your source code. Special functions allow you to add, remove, or change entries in the namespace map.

Namespace Basics

The ns macro has dozens of options for configuring a namespace, so before tackling it you should understand the lower level functions on which it is based.

Switching Namespaces with in-ns

Whenever you are working at the Clojure REPL, the REPL prompt tells you that you are "in" a particular namespace. Clojure always starts in the user namespace:

user=>

Any symbols you define will be created in the user namespace. You can switch to a different namespace with the in-ns function:

user=> (in-ns 'greetings)
#<Namespace greetings>
greetings=>

in-ns takes a symbol argument, and switches to the namespace named by the symbol, creating it if it does not already exist. Please notice in the example that the symbol greetings was quoted to prevent Clojure from trying to evaluate it.

Referring to Other Namespaces

A newly-created namespace does not have any symbols in it, not even the core language functions. If you try to call a built-in Clojure function, you will get an error:

greetings=> (println "Hello, World!")
java.lang.Exception: Unable to resolve symbol: println in this context

Clojure's built-in functions are defined in the namespace clojure.core, and you can refer to them from your new namespace by qualifying the symbols with their namespace:

greetings=> (clojure.core/println "Hello, World!")
Hello, World!
nil

To avoid having to qualify all the symbols you use, you can refer another namespace with the refer function, which is also defined in clojure.core:

greetings=> (clojure.core/refer 'clojure.core)
nil

Now you can call functions in clojure.core directly, without qualification:

greetings=> (println "Hello, World!")
Hello, World!
nil

refer takes a symbol argument and maps all the public symbols from that namespace into the current namespace. (We will cover the difference between public and private symbols later in the section "Public and Private Vars.") The symbols are still mapped to the values in their original namespace. By calling refer in the example, you created a namespace mapping from the symbol greetings/println to the Var # 'clojure .core/println.

refer takes additional options that specify filters for the symbols to be referred. The options take the form of a keyword followed by a list or map of symbols. The :exclude option is followed by a (quoted) list of symbols that should not be referred into the current namespace. For example, the following code:

(refer 'clojure.core :exclude '(map set))

This refers all the symbols in the clojure.core namespace, except map and set. You can then define your own versions of map and set that do not clash with the original definitions in clojure.core.

The :only option is also followed by a list of symbols, but it specifies that only the symbols in the list you specify should be referred into the current namespace. For example, the following code:

(refer 'clojure.core :only '(println prn))

This refers only the two symbols println and prn from clojure.core; other symbols in clojure.core must still be namespace-qualified, like clojure.core/def.

Lastly, refer allows you to rename some symbols when referring them, by including the :rename keyword followed by a map from symbols in the original namespace to symbols in the current namespace.

(refer 'clojure.core :rename {'map 'core-map, 'set 'core-set})

This refers all symbols from clojure.core, but makes the symbol clojure.core/map available in the current namespace as core-map and clojure.core/set available as core-set. This might be useful if you want to define your own version of a built-in function that calls the original version.

As an alternative to copying the mappings from one namespace, you can create a local alias to another namespace, so you can refer to it by a shorter name. Namespace aliases are created with the alias function:

(alias local-name namespace-name)

The arguments local-name and namespace-name are both (quoted) symbols. alias creates an alias in the current namespace to the named namespace. After calling alias, you can reference symbols in the other namespace using local-name, instead of the full namespace name. For example, the following code:

greetings> (alias 'set 'clojure.set)
nil
greetings> (set/union #{1 3 5} #{2 3 4})
#{1 2 3 4 5}

Loading Other Namespaces

refer and alias allow you to reference symbols in namespaces that already exist. But what about namespaces defined in other files, including files that haven't been loaded yet? Clojure provides a variety of functions for loading code from files.

Loading from a File or Stream

The simplest load function is load-file:

(load-file name)

load-file takes one argument, a file name, and attempts to read and evaluate every Clojure form in the file. The file name is given as a String, including any directories, and is interpreted in the context of the current working directory (the directory in which you started Clojure). On a Unix-like system, it might look like the following:

(load-file "path/to/file.clj")

On Windows, back-slashes must be escaped, because the file name is a String:

(load-file "C:\Documents\file.clj")

If you want to load code from some other source, such as a network connection, you can use the load-reader function, which takes a java.io.Reader as its argument, and reads and evaluates code from the Reader.

Loading from the Classpath

The Java Virtual Machine uses a special variable called the classpath, a list of directories from which to load executable code. Clojure programs also use the classpath to search for source files.

The classpath is normally specified on the Java command line as a set of directories and JAR files. The following example, for Unix-like systems, creates a classpath consisting of the Clojure JAR and the / code/sources directory.

java -cp clojure.jar:/code/sources clojure.main

Java development environments and build-management tools usually have their own methods for configuring the classpath; consult your tools' documentation for more information.

Namespace Names vs. File Names

Clojure namespaces follow similar naming conventions to Java packages: they are organized hierarchically with parts separated by periods. A popular convention is to name your libraries using the reversed form of an Internet domain name that you control. So if you work for www.example.com, your namespaces might be named com.example.one, com.example.two, and so on.

When translating between namespace names and file names, periods become directory separators and hyphens become underscores. So, on Unix-like systems, the Clojure namespace com.example.my-cool-library would be defined in the file com/example/my_cool_library.clj. In order to load the namespace, the directory containing com must be on the classpath.

Loading Resources from the Classpath

The load function takes any number of String arguments, each of which names a resource on the classpath. A resource name is like a file name, but without the .clj extension. If the resource name begins with a forward slash (/), it is interpreted as being in some directory on the classpath. For example, the following code:

(load "/com/example/my_library")

This call will search each location on the classpath for the file com/example/my_library.clj. (It will also search for the precompiled class file com/example/my_library.class. Compilation will be covered in Chapter 10.)

If an argument to load does not begin with a slash, it is interpreted as being relative to the directory of the current namespace.

greetings=> (load "hello")

This call to load, from within the greetings namespace, will search the classpath for the file greetings/hello.clj.

Loading Namespaces from the Classpath

You will rarely use the load function in normal code. Instead, Clojure provides two higher level functions, require and use, to load namespaces.

The require function takes any number of arguments, each of which is a symbol, a vector libspec, a prefix list, or a flag. Arguments are typically quoted to prevent evaluation. The simplest case, a symbol, converts the symbol to a file name, searches the classpath for that file, loads it, and verifies that a namespace with the given name was, in fact, created.

(require 'com.example.lib)

This loads the file com/example/lib.clj from the classpath. After loading the file, if the namespace com.example.lib does not exist, require will throw an exception. If the namespace has already been loaded, require will ignore it.

A libspec argument to require allows you to specify options for loading the namespace. It takes the form of a vector, starting with a symbol, followed by keyword options. The only option it accepts (for now) is :as, which creates a local alias to the namespace.

(require '[com.example.lib :as lib])

This loads the namespace com.example.lib and aliases it as lib in the current namespace.

Often several namespaces share a common prefix. In that case, you can use prefix lists to load several namespaces. A prefix list is a list starting with the symbol shared by all the namespaces, followed by the remaining parts of each namespace name. For example, instead of writing:

(require 'com.example.one 'com.example.two 'com.example.three)

You can write this equivalent:

(require '(com.example one two three))

Prefix lists and libspecs can be combined, as in this example:

(require '(com.example one [two :as t]))

This loads the namespaces com.example.one and com.example.two, and creates an alias t for com.example.two.

Lastly, the require function accepts any number of flags, given as keywords anywhere in its arguments. The :reload flag causes require to load all namespaces in the arguments, even if they have already been loaded. For example, the following code:

(require 'com.example.one 'com.example.two :reload)

Another flag, :reload-all, will reload the listed namespaces and all dependent namespaces require'd by those namespaces. The :reload and :reload-all flags are useful when you are experimenting at the REPL and want to load changes you have made in your source files.

The :verbose flag prints debugging information about the lower-level function calls being made by require.

user=> (require '(clojure zip [set :as s]) :verbose)
(clojure.core/load "/clojure/zip")
(clojure.core/load "/clojure/set")
(clojure.core/in-ns 'user)
(clojure.core/alias 's 'clojure.set)
nil

Loading and Referring Namespaces in One Step

Frequently, you may want to require a namespace and also refer certain symbols in it. The use function makes this a one-step operation. Calling use is equivalent to calling require and then refer. use accepts the :reload, :reload-all, and :verbose flags of require; and also the :exclude, :only, and :rename options of refer, grouped in a vector with the namespace they affect. For example, see the following line of code:

(use '[com.example.library :only (a b c)] :reload-all :verbose)

This (re)loads the namespace com.example.library and refers the three symbols a, b, and c into the current namespace. Note that you do not need to quote the list (a b c) because the entire vector is already quoted.

Warning

Except when experimenting at the REPL, it is almost always a bad idea to use a namespace without limiting the symbols it refers with :only. Calling use without :only makes it impossible for readers of your code to know where a particular symbol comes from and can also lead to unexpected name clashes if the use'd namespace changes.

Importing Java Classes

The last namespace function deals with Java classes. You can always refer to a Java class by its fully-qualified name, such as java.util.Date. To refer to a class without its package, you can import it.

user=> (import 'java.util.Date) nil
user=> (new Date)
#<Date Fri Oct 23 16:31:28 EDT 2009>

In Clojure 1.0, import is a function, so you must quote its arguments, as in the example. Starting with Clojure 1.1, import is a macro, and its arguments do not need to be quoted. import also accepts prefix lists similar to require and use. The prefix must be a complete Java package name; the class name may not contain periods.

(import '(java.util.regex Pattern Matcher))

As a special case, nested Java classes (sometimes called "inner classes") must be imported using their binary class name, which the JVM uses internally. The binary class name of an inner class consists of the outer class name, followed by a $ sign, followed by the inner class name. For example, the binary name of a class Wheel nested inside a class Truck is Truck$Wheel.

In Clojure, a nested Java class cannot be named without its enclosing class. For example, to import the nested class javax.swing.Box.Filler, you must do this:

(import '(javax.swing Box$Filler))

After that import, you can refer to the class as Box$Filler.

Bringing It All Together: Namespace Declarations

When writing normal Clojure code, you will not call the in-ns, refer, alias, load, require, use, and import functions directly. Instead, you will typically start your Clojure source file with a namespace declaration using the ns macro, like the example at the start of this chapter.

(ns name & references)

The ns macro takes a symbol as its first argument; it creates a new namespace with that name and sets it to be the current namespace. Because ns is a macro that does not evaluate its arguments, the name does not need to be quoted.

The remaining arguments to the ns macro take the same form as the refer, load, require, use, and import functions, with two differences:

  • Arguments are never quoted.

  • The function name is given as a keyword.

Here's an example.

(ns com.example.library
  (:require [clojure.contrib.sql :as sql])
  (:use (com.example one two))
  (:import (java.util Date Calendar)
           (java.io File FileInputStream)))

This creates a new namespace, com.example.library, and automatically refers the clojure.core namespace. It loads the clojure.contrib.sql namespace and aliases it as sql. It loads the namespaces com.example.one and com.example.two and refers all the symbols from them into the current namespace. Finally, it imports the Java classes Date, Calendar, File, and FileInputStream.

Unlike the in-ns function, the ns macro automatically refers the clojure.core namespace, as previously mentioned. If you want to control which core symbols get referred in your namespace, use the :refer-clojure argument to ns, like this:

(ns com.example.library
  (:refer-clojure :exclude (map set)))

The :refer-clojure form takes the same arguments that you would use with (refer'clojure.core). If you don't want any symbols referred from clojure.core, you can pass an empty list to :only, like (:refer-clojure :only ()).

Symbols and Namespaces

As previously mentioned, namespaces are essentially maps from symbols to Vars, but they have a few unique properties. Symbols can have properties that tie them to specific namespaces.

Namespace Metadata

Like most Clojure objects, namespaces can have metadata (see Chapter 8) attached to them. You can add metadata to the namespace by placing read-time metadata on the symbol in the ns macro, like this:

(ns #^{:doc "This is my great library."
      :author "Mr. Quux <[email protected]>"
   com.example.my-great-library)

While Clojure does not specify any "official" metadata keys for namespaces (like :tag and :arglists for functions) many Clojure library developers have adopted the convention of using :doc metadata to describe the general purpose of a namespace and :author metadata for the author's name and e-mail address.

Forward Declarations

The Clojure compiler requires that symbols be defined before they are used. Usually this leads to organizing your source files with simple, low-level functions at the top and more complex functions at the bottom. But sometimes, you need to use a symbol before it can be defined. To prevent the compiler from throwing an Exception, you must use a forward declaration.

(declare & symbols)

A forward declaration is created with the declare macro, which simply tells the compiler, "This symbol exists, it will be defined later." Here is a contrived, and very inefficient, example:

(declare is-even? is-odd?)

(defn is-even? [n]
  (if (= n 2) true
     (is-odd? (dec n))))

(defn is-odd? [n]
  (if (= n 3) true
     (is-even? (dec n))))

Namespace-Qualified Symbols and Keywords

As you saw earlier, symbols can be qualified with a namespace. The functions name and namespace return the strings representing each part of the symbol:

user=> (name 'com.example/thing)
"thing"
user=> (namespace 'com.example/thing)
"com.example"

Notice that the symbol is quoted to prevent Clojure from trying to resolve it as a class or Var.

The namespace function returns nil for unqualified symbols, which have no namespace:

user=> (namespace 'stuff)
nil

Keywords, too, can be namespace-qualified; the name and namespace functions work as on symbols:

user=> (name :com.example/mykey)
"mykey"
user=> (namespace :com.example/mykey)
"com.example"
user=> (namespace :unqualified)
nil

As a syntactic convenience, you can create keywords in the current namespace by preceding their names with two colons instead of one. In the "user" namespace, the keyword ::thing expands to :user/thing.

user=> (namespace ::keyword)
"user"

Although not explicitly for this purpose, the backquote ` reader macro can be used to create qualified symbols in the current namespace:

user=> `sym
user/sym

Constructing Symbols and Keywords

The name and namespace functions convert from symbols or keywords to strings. The symbol and keyword functions go the other way: given Strings for the name and, optionally, a namespace, they construct a symbol or keyword.

user=> (symbol "hello")
hello
user=> (symbol "com.example" "hello")
com.example/hello
user=> (keyword "thing")
:thing
user=> (keyword "user" "goodbye")
:user/goodbye

Note that the name given to the keyword function does not include the leading colon.

Public and Private Vars

By default, all definitions in a namespace are public, meaning they can be referenced from other namespaces and copied with refer or use. But many namespaces can be divided into two parts: one set of "internal" functions that should never be called from any other namespace and another set of "public" functions meant for use by other namespaces. These correspond, loosely, to the private and public methods of object-oriented languages like Java.

Private Vars in Clojure will never be copied by refer or use, and they cannot be referenced with a namespace-qualified symbol. In effect, they can only be used in the namespace in which they were defined.

There are two ways to create a private Var. The first is the defn- macro, which works exactly like defn but creates a private function definition. The second, which works for any definition, is to add :private metadata to the symbol you are defining.

(def #^{:private true} *my-private-value* 123)

Note that private Vars are never truly hidden; any code can get the value of the Var with (deref (var namespace/name)). But private Vars prevent you from inadvertently calling a function that you did not mean to be used by other parts of your application.

Advanced Namespace Operations

Unlike Java packages, which are simply a naming device, Clojure namespaces are first-class objects, with dedicated functions to query and manipulate them.

Querying Namespaces

The special Var *ns* is always bound to the current namespace. It is changed with in-ns.

The function all-ns takes no arguments and returns a sequence of all namespaces currently defined.

Note

The set of namespaces is global; you cannot have multiple "instances" of Clojure loading different namespaces in the same JVM. It doesn't really make sense to talk about an "instance" of Clojure, since Clojure is just a compiler, not an interpreter like Jython or JRuby. You can create independent execution contexts using Java classloaders, an advanced Java topic outside the scope of this book.

Two functions help you get from a symbol naming a namespace to the namespace object itself. The find-ns function takes a symbol argument and returns the namespace with that name; or nil if no such namespace exists.

Often, you don't care if you're dealing with a namespace object directly or just the symbol naming it. For this purpose, a function called the-ns will accept either a namespace object, in which case it just returns the namespace; or a symbol, in which case it calls find-ns. Unlike find-ns, the-ns throws an Exception if the namespace does not exist. Most of the functions in this section call the-ns on their argument, so they may be called with either a namespace object (such as *ns*) or a quoted symbol.

The ns-name function returns the name of a namespace as a symbol.

The ns-aliases function returns a map, from symbols to namespaces, representing all the namespace aliases defined in a namespace.

The ns-map function returns a map, from symbols to objects (Vars or classes), representing all the mappings in a namespace. Usually, this is more information than you want, so Clojure provides several auxiliary functions that return a subset of the mappings for a namespace. ns-publics returns mappings for all public Vars; ns-interns returns mappings for all Vars (both public and private); ns-refers returns mappings for all symbols referred from other namespaces; and ns-imports returns mappings for all Java classes.

For example, to get a list of all the public symbols in the clojure.core namespace, you can run:

(keys (ns-publics 'clojure.core))

Finally, you may want to find out what a symbol will resolve to when it is encountered in a particular context. The ns-resolve function takes a namespace and a symbol, and returns the Var or class to which that symbol is mapped in the namespace. For example, clojure.core imports the java.math.BigDecimal class, which you can discover by calling:

user> (ns-resolve 'clojure.core 'BigDecimal)
java.math.BigDecimal

As a shortcut, the resolve function is equivalent to ns-resolve for the current namespace.

Manipulating Namespaces

The in-ns function and ns macro both create a namespace and make it the current namespace. Likewise, def and its relatives all operate in the current namespace. There are some special cases, like code generation, where you want to create a namespace and define things in it without switching to it. You may be tempted to write something like this:

;; Bad code!
(let [original (ns-name *ns*)]
  (ns other)
  (defn f [] (println "Function f")
  (in-ns original)))

That won't work, because Clojure reads the symbol f in the current namespace before evaluating the ns form. You'll end up with f defined in the current namespace, not the other namespace.

Instead, you can use the create-ns function, which takes a symbol argument and returns a new namespace with that name (or returns an existing namespace with that name). Then you can use the intern function to define Vars in that namespace. Here's a version of the previous example that actually works:

(let [other-ns (create-ns 'other)]
  (intern other-ns 'f
          (fn [] (println "Function f"))))

The act of creating a Var and mapping it to a symbol in a namespace is called interning the Var, and that's exactly what the intern function does.

(intern namespace symbol value)

The value is optional; if it is omitted, the Var is created with no root value, similar to a forward declaration. The symbol must be a bare symbol, that is, without a namespace-qualifying prefix. The namespace argument may be either a symbol or a namespace.

The ns-unmap function is the opposite of intern; it removes a mapping from a namespace. For example, every Clojure namespace, regardless of how it is created, starts with mappings for all the classes in the java.lang package. If you wanted a completely empty namespace, you could create one like this:

(let [empty-ns (create-ns 'empty)]
  (doseq [sym (keys (ns-map empty-ns))]
    (ns-unmap empty-ns sym))
  empty-ns)

Finally, the remove-ns function will delete a namespace entirely, including all the Vars interned in it. Note that code in other namespaces may still hold references to those Vars in closures, but the Vars themselves are cleared, so any attempt to use them will throw an "unbound Var" Exception.

Namespaces As References

As I said at the beginning of the chapter, a namespace is basically a map from symbols to Vars or classes. It would be more accurate to say it is a reference to a map, because namespaces are mutable. All operations on namespaces are atomic, like Clojure Atoms. For example, if you redefine an existing function with defn, Clojure guarantees that the old and new definitions will never "overlap."

However, Clojure does not provide a way to coordinate namespace operations the way you can with Refs. If you redefine several functions, Clojure cannot guarantee that the "new" functions will all be updated at the same time. There may be a short time in which both old and new definitions are present.

In general, the problem of "hot-swapping" entire modules in a running program is very difficult, and requires support at the deepest levels of the language. Erlang, for example, is designed to support hot-swapping of modules. Java does not have built-in support for hot-swapping, although some Java application servers attempt to provide it.

Summary

There's a lot you can do with namespaces, and they may seem overwhelming at first. But in normal, day-to-day coding you only need a few features and conventions.

First, start every source file with a namespace declaration using ns, using :import and :use expressions to describe the classes and namespaces it depends on. Always use the :only option of :use to make it clear which symbols you need from the other namespace. Here is a complete example:

(ns com.example.apps.awesome
  (:use [clojure.set :only (union intersection)]
        [com.example.library :only (foo bar baz)]
        [com.example.logger :only (log)])
  (:import (java.io File InputStream OutputStream)
           (java.util Date)))

Don't be afraid to reuse good names just because they are part of clojure.core. Add the :refer-clojure expression to ns if needed.

Structure your source files to avoid the need for forward declarations. This usually means placing "primitive" definitions near the top and the "composite" definitions that depend on them toward the bottom.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.74.25