(ns com.some-example.my-app
"My app example"
(:require
[clojure.set :as set]
[clojure.string :as str]))
Namespaces provide a means to organize our code and the names we use in our code. Specifically, they let us give new unambiguous names to functions or other values. These full names are naturally long because they include context. Thus namespaces also provide a means to unambiguously reference the names of other functions and values but using names that are shorter and easier to type.
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.
Vars are associations between a name (a symbol) and a value. Vars in a namespace have a fully-qualified name that is the combination of the namespace name and the var name. For example, clojure.string/join
is a fully-qualified var name where clojure.string
refers to the namespace and join
refers to the var inside the namespace. All vars are globally accessible via their fully-qualified name. By convention vars have lower case names with -
separating words, although this is also not required. Var names may contain most non-whitespace characters.
Vars are created using def
and other special forms or macros that start with def
, like defn
. Vars are created in the "current" namespace. The Clojure runtime tracks the current namespace in the var clojure.core/*ns*
. The current namespace can be changed using the in-ns
function.
In addition to providing a naming context, namespace names also provide a convention for where a namespace’s code should be found for loading. A path is created based on the namespace name:
Periods become directory separators
Hyphens become underscores
The file extension .clj
is added
Thus the namespace name com.some-example.my-app
becomes the load path com/some_example/my_app.clj
. Load paths are searched using the JVM classpath. The classpath is a series of directory locations or JAR files (JARs are essentially just zip files).
When a resource is needed, the JVM searches each classpath location in order for a file at the relative location of the load path. So if the classpath was src:test
, the load path would be checked at src/com/some_example/my_app.clj
then test/com/some_example/my_app.clj
.
There are several ways to load code in Clojure, but most commonly loading is accomplished via require
.
Due to this loading convention, most Clojure is structured with a 1-to-1 mapping of namespaces to files, stored in hierarchical fashion that maps to the namespace structure.
Most Clojure files represent a single namespace and declare the dependencies for that namespace at the top of the file using the ns
macro, which often looks like this:
(ns com.some-example.my-app
"My app example"
(:require
[clojure.set :as set]
[clojure.string :as str]))
The ns
macro specifies the namespace name (this should match the file path location using the conventions above), an optional docstring, and then one or more clauses that declare things about the namespace.
By default, we can refer to or invoke vars in the current namespace without specifying the namespace (the current namespace is the "default").
Additionally, you may have noticed that we can usually refer to clojure.core
library functions without fully qualifying them either. The reason for that is that all of the clojure.core
library vars have been referred
into the current namespace. refer
makes an entry in the current namespace’s symbol table that refers to the var in the other namespace.
The clojure.core
referral is done by the ns
macro. (There are ways to suppress this in part if you’d like to re-use names in core without warnings.)
The :require
clause corresponds to the require
function which specifies one or more namespaces to load that this namespace depends on. For each namespace, require
can do several things:
Load (or reload) the namespace
Optionally assign an alias that can be used to refer to vars from the loaded namespace only in the scope of this namespace
Optionally refer vars from the loaded namespace for use by unqualified name in this namespace
The last two parts are all about making names easier to use. While vars can always be referred to by their fully-qualified name, we rarely want to type fully-qualified names in our code. Aliases let us use shorter versions of longer fully-qualified aliases. Refer allows us to use names without a namespace qualifier at all.
In require
, namespaces most commonly take one of four forms:
clojure.set
- just loads clojure.set
namespace (if not already loaded)
[clojure.set :as set]
- load and create an alias set
for the namespace clojure.set
This allows you to refer to vars in set
with for example set/union
instead of clojure.set/union
[clojure.set :refer [union intersection]]
- load and refer specific vars into this namespace
This allows you to use just union
instead of clojure.set/union
[company.application.component.user :as-alias user]
- create an alias user
for the namespace company.application.component.user
without loading the namespace
Typically, when using :as-alias
, the namespace is being used as a qualifier but is not a loadable namespace
This allows you to use a shorthand for a namespace qualifier, e.g. when creating maps: {::user/id 1}
, registering specs: (s/def ::user/id int?)
or destructuring: (defn find-by-id [{::user/keys [id]}] ,,,)
In addition to vars, Clojure also provides support for Java interop and access to Java classes, which live in packages. Java classes can always be referred to using their fully-qualified class name, such as java.util.Date
.
The ns
macro also imports the classes in the java.lang package so that they can be used as just the class name, rather than the fully-qualified class name. For example, just String
rather than java.lang.String
.
Similar to :refer
, the ns
macro has an :import
clause (that is supported by the import
macro) that lets you import other classes so they can be used with unqualified names:
(ns com.some-example.my-app2
(:import
[java.util Date UUID]
[java.io File]))
This example imports the Date
and UUID
class from the java.util
package and the File
class from the java.io
package.