stats

Read Me Clojure, Pt. 6

Bindings and namespaces

Image

Bindings allow us to identify in-memory objects used in our programs (to give them stable identities), while namespaces make it possible to manage visibility and encapsulate fragments of source code. In this installment, we will learn how to understand these mechanisms in Clojure and how to use them.

Bindings and Namespaces

An important element of computer programming is managing identifiers, that is, human-readable labels that allow us to refer to objects placed in memory.

Bindings

A binding, also called name binding, is a shorthand term for the process of associating in-memory objects (data or subroutines) with identifiers. For example, the letter a can be bound to a memory cell storing the value 123.

Examples of name bindings in Clojure
1(def a 123)     ; global binding
2(let [a 123] a) ; lexical binding
(def a 123) ; global binding (let [a 123] a) ; lexical binding

Thanks to name bindings, we can refer to data placed in memory using readable identifiers in the source code of computer programs instead of, say, memory addresses. Beyond that, they open the way to abstracting data structures, meaning making access to them independent of the hardware architecture.

Even if there existed a computer in which memory addresses were alphanumeric and defined by the programmer, bindings would still be useful because they would decouple data from their location in memory. A binding can be changed while the program is running, whereas a memory address (even the most readable one) cannot. This topic is discussed in more detail in chapter XIII.

Examples of binding names to values might bring to mind operating on variables, but creating an association of a value with an identifier does not necessarily mean that the contents of the memory area where the value resides can be changed. The process of name binding is therefore something more elementary than creating variables and may be a step within that process.

Programming languages may differ in how bindings are created depending on the processing stages at which they arise. We will therefore deal with static binding, also known as early binding, which takes place before the program runs (e.g., during its loading or at compile time), as well as dynamic binding, also known as late binding, where name-to-object bindings are created at runtime.

In Clojure we deal with both of the above-mentioned types of bindings, although the more accurate terms in this case are “static binding” and “dynamic binding,” since when distinguishing between them we do not emphasize the stage of source code translation but rather the characteristics of the bindings themselves (e.g., whether they can be updated).

Software engineering distinguishes two important properties that determine how and where we can use bindings and the values they point to:

  • scope,
  • visibility.

See also:

Scope

The scope of a binding (of an identifier with an in-memory object) is the part of the program in which we can use that binding to refer to the object using the name assigned to it.

The part of the program will most often be a lexical fragment of the source code (e.g., a block, module, function, source code file, etc.), but it may also be considered dynamically and depend on the state of program execution at a given point in time.

A scope that depends on placement is called lexical scope, and a scope that depends on state is called dynamic scope. In the latter case, a special binding stack is maintained for each identifier; this stack may change over time depending on the context and the operations performed on the binding. For example, within a function we can refer to a dynamic variable named d defined outside the function whose value was changed just before the function call. If the scope of d were lexical, we would always refer within the function to the value of d from the moment the function was defined (in the case of languages supporting so-called closures), or it would be unbound to any value within the function.

In Clojure the following kinds of binding scopes are supported:

Examples of name binding scopes in Clojure
 1;; lexical scope
 2;; limited to the body of the let form
 3
 4(let [a 123]  ; binding a symbol to a value
 5  a)          ; using the value identified by the symbol
 6
 7;; unrestricted scope
 8
 9(def a 123)            ; binding a global Var to a value
10a                      ; using the current value identified by the symbol
11
12;; dynamic scope
13
14(def ^:dynamic a 456)  ; binding a dynamic Var to a value
15a                      ; using the current value identified by the symbol
16(binding [a 789]       ; dynamically changing the current value
17  a)                   ; using the changed current value
;; lexical scope ;; limited to the body of the let form (let [a 123] ; binding a symbol to a value a) ; using the value identified by the symbol ;; unrestricted scope (def a 123) ; binding a global Var to a value a ; using the current value identified by the symbol ;; dynamic scope (def ^:dynamic a 456) ; binding a dynamic Var to a value a ; using the current value identified by the symbol (binding [a 789] ; dynamically changing the current value a) ; using the changed current value

Visibility

Visibility is the part of the program in which we can access and use the value associated with an identifier and stored in a memory structure. We will say, then, that (depending on the language) the following is visible: a variable, a constant, a value, or another construct responsible for storing data.

An example of the difference between visibility and scope is a situation where an in-memory object (e.g., the integer 2) identified by a symbolic name (e.g., x) in a certain lexical region (e.g., inside a function definition) ceases to be visible because the name x was used as a function parameter whose purpose is to refer to the value of the passed call argument. The value 2 does not disappear, but its identification is temporarily shadowed within the function definition region. When the source code fragment defining the function ends, x will still refer to the value 2. We will therefore speak of the scope of x (or of the binding of x to a value), but of the visibility of the value 2 bound to x.

The visibility of an identified object will never be greater than the scope of the binding, but the scope of the binding may be greater than the visibility – as in the example above.

In Clojure we can control visibility by using namespaces and designating global identifiers as private or public, while scope depends on the kind of constructs used and on the context.

Namespaces

In Clojure there is a namespace mechanism. Thanks to it, one can organize individual program elements and separate symbolic identifiers that originate from different sources in order to avoid naming conflicts.

A namespace is, in the abstract sense, a dictionary of terms, each of which should be unique within it. In a more approachable way, we can describe a Clojure namespace as a uniquely named container for storing globally accessible bindings.

Programs written in Clojure that create bindings with unrestricted scope (used to identify functions or references to values) will always use namespaces because that is where bindings are placed.

Current Namespace

How does the compiler know which namespace is in question? First, it can use the currently set current namespace. When we look at the source code file of any program, we will see at the beginning a notation similar to the following:

(ns smangler.api
  (:require [clojure.string :as    string]
            [clojure.set    :refer    all]))
(ns smangler.api (:require [clojure.string :as string] [clojure.set :refer all]))

It tells the compiler that during further loading of the source code it should set the special reference identified by the symbol *ns* to a value corresponding to an object of type clojure.lang.Namespace, that is, to a namespace. If the namespace with the given name (in this example smangler.api) does not yet exist, it will be created. The consequence of this operation is that every binding with unrestricted scope created thereafter will be placed in that namespace, or more precisely, in a special map contained within its object.

The following code will cause a binding of the symbol a to an object of type Var referring to the value 1 to appear in the current namespace (e.g., smangler.api):

(def a 1)
(def a 1)

Every unquoted symbol used in the source file after the occurrence of ns that has not been lexically bound will be looked up in the current namespace (indicated by the current value of *ns*) in order to find the construct associated with it that holds a reference to some value.

The macro ns supports quite a few additional options, but :require is the most commonly used. It causes so-called aliases to be created in the namespace, which allow referring to other namespaces (the :as option). It can also be used to place bindings from another namespace into the current one so that symbols do not need to be prefixed with its name (the :refer option).

We can see, then, that when bringing functions or global Vars into existence in Clojure, the programmer must choose in which namespace their symbolic identifiers will reside. The programmer is then able to refer to two different constructs with the same names but registered in different namespaces, e.g., the function sum from the namespace light-years and the function sum from the namespace rational-numbers (using the notation light-years/sum and rational-numbers/sum). The programmer may also decide that certain bindings originating from other namespaces should be mirrored in the current namespace if their names are sufficiently unique, and may even rename them.

Qualified Namespace

We can also refer to global identifiers located in namespaces using a special symbolic form in which, in addition to the proper identifying name, we also find the namespace name, e.g.:

(clojure.core/println 1 2 3)
(clojure.core/println 1 2 3)

The above notation means that we want to call the function identified by the name println from the namespace clojure.core. To locate the function’s subroutine, the compiler will use the namespace we specified instead of the one indicated by the current value of the variable *ns*.

Namespace Structure

In a more systematic way, we will characterize a namespace as an object (of type clojure.lang.Namespace):

  • serving to store:

  • containing two associative structures:

    • an aliases map,
    • a mappings map;
  • registered in a global repository at the moment of creation so that language mechanisms can use it during the automatic resolution of symbolic identifiers to their corresponding in-memory objects holding values.

The function of the aliases map present in a namespace is to create references to other namespaces using alternative identifiers assigned to them in the form of symbols.

Aliases map in Clojure

The namespace aliases map associates symbols with objects of other namespaces

Instead of prefixing every symbol name with a long namespace name (e.g., io.randomseed.blog.calc/dodaj), we can create an alias for it in the current namespace (e.g., calc) and use a shortened version when referring to identified values or functions (e.g., calc/add).

The mappings map contains pairs of elements in which the keys are also symbols, and their associated values can be:

  • objects of type Var,
  • references to Java classes.

It is worth noting that an object of type Var placed in a namespace is called a global Var. Global Vars in Clojure are used to identify configuration information or subroutines (e.g., functions or macros).

Mappings map in Clojure

The namespace mappings map associates symbols with Var objects or Java classes

Among the values found in the mappings map we can find not only Vars created and assigned to the same namespace, but also objects residing in other namespaces.

References to Var objects from other namespaces are created using the function refer or use, references to Java classes using import, while new global Vars are automatically mapped at the moment of their creation (e.g., using def or intern).

Public and Private Vars

Worth noting is the fact that mappings in a namespace can be marked as private. They will then not be visible outside the namespace in which they were placed, meaning:

  • They can be referred to from inside functions that were defined in the same namespace.

  • They can be referred to when the currently set namespace is the one they belong to. This is an additional way of controlling visibility.

Namespace Initialization

When the Clojure runtime environment is being prepared, the clojure.core namespace is initialized. The symbol *ns* identifies a global dynamic variable defined in clojure.core, and its current value points to the current namespace object. In a REPL session this value usually points to the user namespace.

Public Vars from clojure.core are referred into the current namespace, which makes core functions and macros available without namespace qualification. Special forms are handled directly by the compiler and are not stored as namespace mappings.

Among the automatically added references, we will find, among others, those that identify the following functions:

  • in-ns – used to set the current namespace,

  • import – assigns the class names of a given Java package to identifiers in the current namespace,

  • refer – assigns symbols to Var objects from another namespace in the current namespace.

Thanks to this, the programmer can further extend the namespace’s contents with new mappings.

The functions presented further on provide more or less general access to namespaces. In practice, however, they are rarely used directly; instead, one uses the ns macro.

Creating Namespaces

To use a given namespace, it must first be created. Some namespaces are created automatically, e.g., clojure.core and user (when using the REPL). However, if we wanted to create a namespace on our own, for example to encapsulate a library we are building or to better manage visibility in an application, we can use the appropriate function.

Creating a Namespace, create-ns

The function create-ns takes a namespace name in the form of a literal symbol and creates the given namespace if it does not yet exist. If a namespace with the given name has already been created, no action is taken.

Usage:

  • (create-ns symbolic-name).

The function takes a namespace name expressed as a symbol in constant form and returns a namespace object (of type clojure.lang.Namespace).

Example of creating a namespace using the create-ns function
1(create-ns 'nowa)
2; => #<Namespace nowa>
(create-ns &#39;nowa) ; =&gt; #&lt;Namespace nowa&gt;

See also:

Using Namespaces

Using namespaces involves referring to various objects by means of symbolic forms, that is, symbols in unquoted form that identify other objects. By placing such a symbol in the program’s source code, we cause the appropriate namespace to be searched (using the resolve function), and then the value of the object identified by the symbolic name is retrieved.

Let us recall that symbols can have a qualified namespace or contain no namespace information. This property allows using them in two ways and translates into different modes of referring to namespaces.

When we provide a symbol without specifying a namespace in it (e.g., replace), the current namespace will be searched. If, on the other hand, we specify a namespace in the symbol (e.g., clojure.string/replace), the specified namespace will be searched.

After the search operation, the object associated with the symbolic name will be retrieved from the namespace. It will be either a global Var of type Var or a Java class. If we are dealing with a reference to an object in another namespace, it will be used to obtain it.

Setting the Current Namespace, in-ns

The current namespace can change dynamically at the programmer’s will. The reference to it resides in a global dynamic variable named *ns*. We are therefore able, within a given source file (or even within an expression passed to the binding macro), to switch the namespace by replacing the value of this variable in the chosen context. This context need not be lexical, although a common practice is to set the current namespace at the beginning of a source file whose entire set of function, macro, and global Var definitions will, during its loading, register global bindings in the chosen namespace by default.

To avoid having to think about how the information about the current namespace is stored, and thus about the methods of operating on it, we can use a ready-made function designed for switching the current namespace. It is called in-ns.

Usage:

  • (in-ns symbolic-name).

The function takes one argument, which should be a constant form of a symbol. If the namespace designated by it does not yet exist, the function create-ns will be called to create it. After the function executes, the binding of the dynamic, global variable *ns* will be replaced and the namespace object will be returned.

Example of switching the current namespace using the in-ns function
1(in-ns 'nowa)
2; => #<Namespace nowa>
(in-ns &#39;nowa) ; =&gt; #&lt;Namespace nowa&gt;

After executing the above code in the REPL, we may be surprised that the interpreter does not “see” language functions that were previously reachable by providing their symbolic identifiers. This happens because we did not import into the newly created namespace the symbol bindings present in the user namespace, which the REPL uses by default.

To use names bound to global Vars from another namespace, one must use the (discussed further on) function refer, which will create appropriate references. There is also a convenient macro ns, which will be discussed later.

Determining the Name, namespace

Thanks to the function namespace, we can obtain the namespace name from a given symbol (in literal form) that contains a namespace name.

Usage:

  • (namespace symbolic-name).

This function does not perform a namespace search; it simply extracts from the symbol passed as an argument the appropriate component of its name.

The return value is a text string or nil if we are dealing with a symbol without a qualified namespace.

Example of using the namespace function
1(namespace 'przestrzeń/jakaś-nazwa)
2; => "przestrzeń"
(namespace &#39;przestrzeń/jakaś-nazwa) ; =&gt; &#34;przestrzeń&#34;

Resolving by Names, ns-resolve

Thanks to the function ns-resolve, we can obtain the object identified by a symbol if a symbol of that name has been assigned to it in the namespace given as the first argument (as a literal symbol or namespace object). The last argument is a symbol in constant form that identifies the sought object.

The function returns an object with the given name expressed as a literal symbol, or the value nil if no mapping was found in the given namespace.

The namespace being searched must exist; if it does not, an exception will be thrown.

In the three-argument variant, the function takes as its second argument the name of a so-called environment. This can be any collection on which the contains? function can be called (e.g., a set or a map). If the given symbol is found in the environment, the namespace will not be searched and the function will return nil.

Usage:

  • (ns-resolve namespace symbolic-name),
  • (ns-resolve namespace environment symbolic-name).
Examples of using the ns-resolve function
1(ns-resolve 'user 'replace)                  ; => #'clojure.core/replace
2(ns-resolve *ns*  'replace)                  ; => #'clojure.core/replace
3(ns-resolve 'clojure.string 'replace)        ; => #'clojure.string/replace
4(ns-resolve 'clojure.string 'cośtam)         ; => nil
5(ns-resolve *ns* #{'replace 'coś} 'replace)  ; => nil
(ns-resolve &#39;user &#39;replace) ; =&gt; #&#39;clojure.core/replace (ns-resolve *ns* &#39;replace) ; =&gt; #&#39;clojure.core/replace (ns-resolve &#39;clojure.string &#39;replace) ; =&gt; #&#39;clojure.string/replace (ns-resolve &#39;clojure.string &#39;cośtam) ; =&gt; nil (ns-resolve *ns* #{&#39;replace &#39;coś} &#39;replace) ; =&gt; nil

Resolving in the Namespace, resolve

The function resolve is a less demanding version of ns-resolve. It does not require specifying a namespace name because it uses the current one by default (determined by the global variable *ns*). However, one can provide a symbol in constant form with a qualified namespace, and the function will use that information. The return value is an object of type Var.

Usage:

  • (resolve symbolic-name),
  • (resolve environment symbolic-name).
Examples of using the resolve function
 1(resolve 'replace)
 2; => #'clojure.core/replace
 3
 4;; providing an environment that contains the symbol
 5
 6(resolve #{'replace} 'replace)
 7; => nil
 8
 9;; providing a symbol with a qualified namespace
10
11(resolve 'clojure.string/replace)
12; => #'clojure.core/replace
(resolve &#39;replace) ; =&gt; #&#39;clojure.core/replace ;; providing an environment that contains the symbol (resolve #{&#39;replace} &#39;replace) ; =&gt; nil ;; providing a symbol with a qualified namespace (resolve &#39;clojure.string/replace) ; =&gt; #&#39;clojure.core/replace

Managing Namespaces

Thanks to the functions all-ns and find-ns, we can search for namespaces and retrieve their lists.

Retrieving the List of Namespaces, all-ns

The function all-ns takes no arguments and returns a sequence (an object of type clojre.lang.IteratorSeq) containing all defined namespaces in the form of the objects representing them.

Usage:

  • (all-ns).
Example of using the all-ns function
1(all-ns)
2; => (#<Namespace reply.main> #<Namespace clojure.tools.nrepl.misc> … )
(all-ns) ; =&gt; (#&lt;Namespace reply.main&gt; #&lt;Namespace clojure.tools.nrepl.misc&gt; … )

Finding a Namespace, find-ns

The function find-ns returns the namespace object whose name, specified by a symbol in constant form, was given as its first argument. If a namespace with the given name does not exist, the value nil is returned.

Usage:

  • (find-ns namespace-name).
Example of using the find-ns function
1(find-ns 'user)
2; => #<Namespace user>
(find-ns &#39;user) ; =&gt; #&lt;Namespace user&gt;

Removing a Namespace, remove-ns

Namespaces can not only be added but also removed. This operation is possible using the function remove-ns. It takes one argument, which is the name of the namespace to be removed expressed as a symbol in constant form.

The function returns nil when the namespace does not exist, or the namespace object when removal was performed. It cannot be used to remove the clojure.core namespace.

Usage:

  • (remove-ns namespace-name).
Examples of using the remove-ns function
1(remove-ns 'clojure.string)
2; => nil
(remove-ns &#39;clojure.string) ; =&gt; nil

Managing Mappings

Bindings of symbols to global Vars or Java objects can be added to namespaces using one of several functions designed for this purpose.

Adding References to Vars, refer

The function refer allows adding references to Var objects placed in other namespaces within the current namespace. As its first argument it takes the name of the source namespace in the form of a constant symbol, and as subsequent, optional arguments, filters that will be used to refine the operations performed.

The function returns nil, and in the case of a nonexistent namespace, an exception is thrown.

Usage:

  • (refer namespace-name & filters).

As a result of calling refer, all symbols pointing to Var objects will be retrieved from the given namespace, and then references with the same symbolic names will be created in the current namespace, unless appropriate filters were used.

In the case where a reference being created has the same symbolic name as an already existing mapping, it will be overwritten, and an appropriate warning will be sent to the standard diagnostic output.

In the current namespace, Var objects residing in the source namespace will be directly associated with symbols. When a binding in the original namespace is removed (e.g., using ns-unmap), the current namespace will still contain the mapping of the symbol to the Var object.

The available filters are:

  • :exclude symbol-sequence – symbols to skip,
  • :only symbol-sequence – symbols to process exclusively,
  • :rename symbol-map – symbols to rename.

These filters should consist of a name in the form of a keyword, followed by a sequence of symbols specifying the filter values, which will be symbolic names to include (:only) or skip (:exclude).

The sequences can be any collections (e.g., vectors) equipped with a sequential access interface. If renaming was requested (:rename), then instead of a sequence one should provide a map containing transformation pairs. In this way, a given global Var can be referred to in the current namespace under a different symbolic name.

Examples of using the refer function
 1;; create references to all Vars from clojure.string
 2
 3(refer 'clojure.string)
 4; => nil
 5
 6;; create a reference only to the identifier 'replace'
 7
 8(refer 'clojure.string :only '[replace])
 9; => nil
10
11;; create references to all Vars from clojure.string
12;; except 'replace' and 'reverse'
13
14(refer 'clojure.string :exclude '[replace reverse])
15; => nil
16
17;; create references to all Vars from clojure.string
18;; except 'replace', and rename 'reverse' to 'nazad'
19
20(refer 'clojure.string
21       :exclude '[replace]
22       :rename  '{reverse nazad})
23; => nil
;; create references to all Vars from clojure.string (refer &#39;clojure.string) ; =&gt; nil ;; create a reference only to the identifier &#39;replace&#39; (refer &#39;clojure.string :only &#39;[replace]) ; =&gt; nil ;; create references to all Vars from clojure.string ;; except &#39;replace&#39; and &#39;reverse&#39; (refer &#39;clojure.string :exclude &#39;[replace reverse]) ; =&gt; nil ;; create references to all Vars from clojure.string ;; except &#39;replace&#39;, and rename &#39;reverse&#39; to &#39;nazad&#39; (refer &#39;clojure.string :exclude &#39;[replace] :rename &#39;{reverse nazad}) ; =&gt; nil

See also:

Adding Clojure References, refer-clojure

A variant of the refer function is the refer-clojure macro, which can be treated as syntactic sugar since it calls refer with the first argument value set to 'clojure-core.

refer-clojure is often used when we want to opt out of automatically creating references to all functions and macros defined in the main Clojure library and select only those we use in a given source file. Usually it will then be called indirectly, through one of the clauses of the ns macro.

When might we need this? For example, when our library defines in its own namespace functions or macros with the same names as in clojure.core. We then avoid warnings about shadowing existing identifiers.

The macro returns nil.

Usage:

  • (refer-clojure & filters).

As a result of evaluating the code produced by the macro, references to Var objects originating from clojure.core will be added in the current namespace. Subsequent, optional arguments may express filters that will be used to refine the operations performed.

In the case where a reference being created has the same symbolic name as an already existing mapping in the current namespace, it will be overwritten, and an appropriate warning will be sent to the standard diagnostic output.

In the current namespace, Var objects residing in clojure.core will be directly associated with symbols. When a binding in the original namespace is removed (e.g., using ns-unmap), the current namespace will still contain the mapping of the symbol to the Var object.

The available filters are:

  • :exclude symbol-sequence – symbols to skip,
  • :only symbol-sequence – symbols to process exclusively,
  • :rename symbol-map – symbols to rename.

These filters should consist of a name in the form of a keyword, followed by a sequence of symbols specifying the filter values, which will be symbolic names to include (:only) or skip (:exclude).

The sequences can be any collections (e.g., vectors) equipped with a sequential access interface. If renaming was requested (:rename), then instead of a sequence one should provide a map containing transformation pairs. In this way, a given global Var can be referred to in the current namespace under a different symbolic name.

Example of using the refer-clojure macro
 1;; remove bindings for apply and format
 2;; from the current namespace
 3
 4(ns-unmap *ns* 'apply)
 5(ns-unmap *ns* 'format)
 6
 7;; create references to all Vars from clojure.core
 8;; except apply and format
 9
10(refer-clojure :exclude '[apply format])
11; => nil
12
13;; no warning when defining apply
14;; in the current namespace
15
16(defn apply [] "aplikacja")
;; remove bindings for apply and format ;; from the current namespace (ns-unmap *ns* &#39;apply) (ns-unmap *ns* &#39;format) ;; create references to all Vars from clojure.core ;; except apply and format (refer-clojure :exclude &#39;[apply format]) ; =&gt; nil ;; no warning when defining apply ;; in the current namespace (defn apply [] &#34;aplikacja&#34;)

Adding References to Java Classes, import

The import macro allows adding to a namespace mappings of symbols to references referring to Java classes located in a given package.

The accepted arguments may be individual symbols – meaning that specific classes placed in packages are to be imported. The argument may also be a list of symbols – in that case the first one designates the package (e.g., java.util), and the subsequent ones are names of classes from that package (e.g., Date).

As a result of the macro’s operation, symbolic names identical to the Java class names will be added to the current namespace, along with references to those classes associated with them.

The function returns the object value of the last imported class, and in the case of a nonexistent package or class name, an exception is thrown.

Usage:

  • (import [& symbol…] & symbol-list…).

Where symbol-list is:

  • (package-symbol class-name-symbols).
Examples of using the import function
 1;; importing individual classes
 2
 3(import java.util.Date)
 4
 5;; importing selected classes from a package
 6
 7(import '(java.util Date Dictionary))
 8
 9;; using an object
10
11(Date.)
12; => #inst "2015-04-02T11:35:43.980-00:00"
;; importing individual classes (import java.util.Date) ;; importing selected classes from a package (import &#39;(java.util Date Dictionary)) ;; using an object (Date.) ; =&gt; #inst &#34;2015-04-02T11:35:43.980-00:00&#34;

The import function can also be used to import new data types created in Clojure (e.g., using deftype or defrecord).

See also:

Interning Var Objects, intern

The function intern serves to intern objects of type Var, thereby creating global Vars. It takes two arguments: a namespace name expressed as a symbol in constant form or a namespace object, and a variable name in the form of a constant symbol.

Using intern causes a mapping of the given symbol to an object of type Var to be created in the specified namespace. If a Var object already exists under the given name, it will not be replaced by a new one.

In the three-argument version, the function initializes the object with the given value, meaning it sets inside the global Var a reference pointing to the indicated in-memory object (the so-called root binding of the global Var). If the global Var already exists, its reference will be updated without creating a new Var object.

The function returns the Var object identified by the symbol.

Note: The function intern replaces already existing references to Java classes in the namespace with Var objects of the same names, even when no initial value has been set.

Usage:

  • (intern namespace symbolic-name & initial-value).
Examples of using the intern function
1(intern 'user 'zmienna)         ; => #'user/zmienna
2(intern 'user 'zmienna 5)       ; => #'user/zmienna
3user/zmienna                    ; => 5
4zmienna                         ; => 5
5(intern (find-ns 'user) 'a 10)  ; => #'user/a
(intern &#39;user &#39;zmienna) ; =&gt; #&#39;user/zmienna (intern &#39;user &#39;zmienna 5) ; =&gt; #&#39;user/zmienna user/zmienna ; =&gt; 5 zmienna ; =&gt; 5 (intern (find-ns &#39;user) &#39;a 10) ; =&gt; #&#39;user/a

We can cause the interned global Var to be treated as private, meaning it will be visible only in the namespace to which it was added. In practice this will mean that only constructs from areas of the program where the current namespace is set to the same one as the namespace of the defined variable can refer to such a variable using its identifier. To mark a global Var as private, one should use the so-called symbol metadata on the passed name argument. Specifically, this involves the metadata designated by the key :private.

Example of using the intern function to create private bindings
1(intern 'user '^:private zmienna)                      ; => #'user/zmienna
2(intern 'user '^{:private true} zmienna-2)             ; => #'user/zmienna
3(intern 'user (with-meta 'zmienna-3 {:private true}))  ; => #'user/zmienna
(intern &#39;user &#39;^:private zmienna) ; =&gt; #&#39;user/zmienna (intern &#39;user &#39;^{:private true} zmienna-2) ; =&gt; #&#39;user/zmienna (intern &#39;user (with-meta &#39;zmienna-3 {:private true})) ; =&gt; #&#39;user/zmienna

The complete list of metadata that are relevant when interning a Var object is given in the description of the special form def.

Var objects can be updated by calling the intern function again. Updating consists of binding them to new values. If the variable already exists, its object in the namespace will not be replaced by another one; its internal reference to a specific value will simply be changed.

Example of changing the root binding
1(intern 'user 'xx 5)      ; creation and binding to value 5
2
3(pprint #'xx)             ; displaying the reference object
4; >> #<Var@7a0ad359: 5>
5
6(intern 'user 'xx 7)      ; binding to value 7
7(pprint #'xx)             ; displaying the reference object
8; >> #<Var@7a0ad359: 7>
(intern &#39;user &#39;xx 5) ; creation and binding to value 5 (pprint #&#39;xx) ; displaying the reference object ; &gt;&gt; #&lt;Var@7a0ad359: 5&gt; (intern &#39;user &#39;xx 7) ; binding to value 7 (pprint #&#39;xx) ; displaying the reference object ; &gt;&gt; #&lt;Var@7a0ad359: 7&gt;

Note: The function intern can be used to change the root binding, which is shared across threads, even when we are inside a construct that isolates the variable within a thread (e.g., in a dynamic scope).

Adding Var Objects, def

The special form def works similarly to intern, but operates on the current namespace and requires an unquoted symbol as the first argument. The symbol in this context will not create a symbolic form or a constant form, but rather a binding form.

The def form takes one mandatory argument (the aforementioned symbol whose name is to be associated with the created Var object) and two optional arguments: a documentation string and an initial value for the global Var (which will be used for updating if the variable already exists).

If a symbol with a qualified namespace is provided, it must be the current namespace – otherwise an exception will be thrown. An exception will also occur when def is used to update a mapping that identifies a Java class or is a reference to an object from another namespace.

The function returns a Var object identified by the symbol, which is identical to the object placed in the namespace.

Usage:

  • (def symbol documentation-string? initial-value?).
Examples of using the def function
 1(def zmienna)                   ; => #'user/zmienna
 2(def zmienna 5)                 ; => #'user/zmienna
 3(def zmienna "dokumentacja" 5)  ; => #'user/zmienna
 4user/zmienna                    ; => 5
 5zmienna                         ; => 5
 6
 7;; accessing the documentation – the doc function
 8
 9(doc zmienna)
10; >> user/zmienna
11; >>  dokumentacja zmiennej
12; => nil
(def zmienna) ; =&gt; #&#39;user/zmienna (def zmienna 5) ; =&gt; #&#39;user/zmienna (def zmienna &#34;dokumentacja&#34; 5) ; =&gt; #&#39;user/zmienna user/zmienna ; =&gt; 5 zmienna ; =&gt; 5 ;; accessing the documentation – the doc function (doc zmienna) ; &gt;&gt; user/zmienna ; &gt;&gt; dokumentacja zmiennej ; =&gt; nil

In the case of def, one can also add metadata to the symbol indicating that the mapping in the namespace should be private, that is, visible only to constructs from the same namespace.

Metadata associated with the symbol will be copied to the Var object during its creation.

Examples of using the def function to create a private global Var
1(def ^:private zmienna)             ; => #'user/zmienna
2(def ^{ :private true } zmienna 5)  ; => #'user/zmienna
(def ^:private zmienna) ; =&gt; #&#39;user/zmienna (def ^{ :private true } zmienna 5) ; =&gt; #&#39;user/zmienna

Below is a list of all metadata that are significant when using def:

Key Type Meaning
:private java.lang.Boolean Boolean flag indicating that the variable should be private
:dynamic java.lang.Boolean Boolean flag indicating that the variable should be dynamic
:doc string java.lang.String A text string documenting the variable's identity
:tag object Class or Symbol A symbol constituting the name of a class or a Class object that indicates the type of the Java object contained in the variable (unless it is a function – then it will be its return value)
:test function (implementing IFn) A zero-argument function used for testing (the variable object will be accessible in it as an fn literal placed in the metadata)

During the creation of a Var object, the following metadata will be automatically placed in it:

Key Type Meaning
:file java.lang.String Source file name
:line java.lang.Integer Source file line number
:name clojure.lang.Symbol Variable name
:ns clojure.lang.Namespace Namespace
:macro java.lang.Boolean Flag indicating that the object refers to a macro
:arglists PersistentVector$ChunkedSeq A vector sequence with arguments, if the object refers to a function or macro

Global Vars can be updated by, among other means, calling the def function again. This involves binding the reference inside the Var object to a new value. If the variable identified by the given symbol already exists, its object in the namespace will not be replaced by another one, but its reference to a specific value (the root binding) will be changed.

Example of changing the root binding of a Var object
1(def xx 5)               ; creation and binding to value 5
2(pprint #'xx)            ; displaying the object
3; >> #<Var@4eee52c: 5>
4
5(def xx 7)               ; binding to value 7
6(pprint #'xx)            ; displaying the object
7; >> #<Var@4eee52c: 7>
(def xx 5) ; creation and binding to value 5 (pprint #&#39;xx) ; displaying the object ; &gt;&gt; #&lt;Var@4eee52c: 5&gt; (def xx 7) ; binding to value 7 (pprint #&#39;xx) ; displaying the object ; &gt;&gt; #&lt;Var@4eee52c: 7&gt;

Note: The special form def can be used to change the root binding, which is shared across threads, even when we are inside a construct that isolates the variable within a thread (e.g., in a dynamic scope).

One-Time Adding of Vars, defonce

The defonce macro allows creating a Var object and placing it in the current or a specified (by an unquoted symbol) namespace. It works similarly to def, but does not update the binding when the Var object already has one.

The macro does not allow setting documentation strings because it accepts only two arguments: an unquoted symbol (which may contain namespace information) and an expression whose value, once evaluated, will become the value of the variable’s root binding.

The macro returns a Var object if the root binding was set, or the value nil if the binding already existed.

The defonce macro is useful when naming function objects and everywhere one needs to refer to a constant value that should be the result of the first evaluation of some expression or the first retrieval of data from an external source.

Note: Even if the binding to a value did not take place, the metadata originating from the passed symbol will be set and will replace the previous ones.

Usage:

  • (defonce symbol expression).
Examples of using the defonce function
 1(defonce zmienna)                 ; => #'user/zmienna
 2(defonce zmienna 5)               ; => #'user/zmienna
 3(defonce ^:flaszka zmienna 1000)  ; => #'user/zmienna
 4user/zmienna                      ; => 5
 5zmienna                           ; => 5
 6
 7(meta #'zmienna)
 8; => { :ns #<Namespace user>, :name zmienna,
 9; =>   :flaszka true, :file "NO_SOURCE_PATH",
10; =>   :column 1, :line 1 }
(defonce zmienna) ; =&gt; #&#39;user/zmienna (defonce zmienna 5) ; =&gt; #&#39;user/zmienna (defonce ^:flaszka zmienna 1000) ; =&gt; #&#39;user/zmienna user/zmienna ; =&gt; 5 zmienna ; =&gt; 5 (meta #&#39;zmienna) ; =&gt; { :ns #&lt;Namespace user&gt;, :name zmienna, ; =&gt; :flaszka true, :file &#34;NO_SOURCE_PATH&#34;, ; =&gt; :column 1, :line 1 }

Removing Mappings, ns-unmap

Thanks to the function ns-unmap, we can remove from a namespace bindings of symbols to global Vars or Java classes. It takes two arguments. The first specifies the namespace (using a symbol in constant form or a namespace object), and the second is the symbolically expressed name of the specific mapping to be removed.

The function returns nil, and when the given namespace does not exist, an exception is thrown.

Usage:

  • (ns-unmap namespace symbolic-name).
Example of using the ns-unmap function
 1;; we create a global Var x
 2
 3(def x 5)
 4; => #'user/x
 5
 6;; we retrieve its value
 7
 8x
 9; => 5
10
11;; additionally we create a reference to the Var object of this variable
12
13(def y (var x))
14
15;; we remove the mapping
16
17(ns-unmap 'user 'x)
18; => nil
19
20;; we check whether the identifier x is still visible
21
22(resolve 'x)
23; => nil
24
25;; we check whether the Var object itself exists,
26;; although it is no longer bound to the symbol x
27
28(deref y)
29; => 5
;; we create a global Var x (def x 5) ; =&gt; #&#39;user/x ;; we retrieve its value x ; =&gt; 5 ;; additionally we create a reference to the Var object of this variable (def y (var x)) ;; we remove the mapping (ns-unmap &#39;user &#39;x) ; =&gt; nil ;; we check whether the identifier x is still visible (resolve &#39;x) ; =&gt; nil ;; we check whether the Var object itself exists, ;; although it is no longer bound to the symbol x (deref y) ; =&gt; 5

In the example above, we also showed that when a mapping is removed from a namespace, the Var class object itself (or another object) is not destroyed but loses its name.

Adding Aliases, alias

The alias mechanism allows referring to different namespaces using alternative identifiers placed in the current namespace.

The function alias allows adding alternative names for other namespaces to the current namespace. It takes two arguments: the first should be a symbol in constant form, and the second a namespace object or its name expressed as a literal symbol. The first argument is the alias name, and the second is the namespace to which a reference is to be created.

The function returns nil, and in the case of specifying a nonexistent namespace, an exception is thrown.

Usage:

  • (alias symbolic-name namespace).
Example of using the alias function
1(alias 'st 'clojure.string)
2(st/reverse "abcdef")
3; => "fedcba"
(alias &#39;st &#39;clojure.string) (st/reverse &#34;abcdef&#34;) ; =&gt; &#34;fedcba&#34;

See also:

Removing Aliases, unalias

The function ns-unalias removes aliases added using alias. It takes two arguments. The first should specify the namespace using a constant symbol form or a namespace object, and the second should be the alias name expressed symbolically.

The function always returns nil, regardless of whether the given alias existed or whether its removal was not possible because it was not in fact an alias. When the given namespace does not exist, an exception is thrown.

Usage:

  • (ns-unalias namespace symbolic-name).
Example of using the ns-unalias function
1(ns-unalias 'user 'st)
2; => nil
(ns-unalias &#39;user &#39;st) ; =&gt; nil

Reading Contents

Reading the name, ns-name

The function ns-name takes a namespace object (or a symbol representing its name) as an argument, and returns a symbol identifying the namespace’s name. If the namespace does not exist, an exception is thrown.

Usage:

  • (ns-name namespace).
Usage examples for the ns-name function
1(ns-name 'user)            ; => user
2(ns-name (find-ns 'user))  ; => user
(ns-name &#39;user) ; =&gt; user (ns-name (find-ns &#39;user)) ; =&gt; user

Reading aliases, ns-aliases

The function ns-aliases takes a single argument, which should be a symbol in literal form specifying the name of a namespace or an object of that namespace, and returns a map containing defined aliases, that is, mappings of other namespace objects to symbolic identifiers. If the namespace does not exist, an exception will be thrown.

Usage:

  • (ns-aliases namespace).
Usage examples for the ns-aliases function
1(ns-aliases 'clojure.core)     ; => {jio #<Namespace clojure.java.io>}
2(alias 'stri 'clojure.string)  ; => nil
3(ns-aliases 'user)             ; => {stri #<Namespace clojure.string>}
(ns-aliases &#39;clojure.core) ; =&gt; {jio #&lt;Namespace clojure.java.io&gt;} (alias &#39;stri &#39;clojure.string) ; =&gt; nil (ns-aliases &#39;user) ; =&gt; {stri #&lt;Namespace clojure.string&gt;}

Reading Var mappings, ns-interns

The function ns-interns takes a single argument, which should be a symbol in literal form specifying the name of a namespace or an object of that namespace, and returns a map containing mappings of symbolic identifiers to global variables (objects of type Var). If the namespace does not exist, an exception will be thrown.

Usage:

  • (ns-interns namespace).
Usage example for the ns-interns function
1(ns-interns 'user)
2; => {apropos-better #'user/apropos-better, cdoc #'user/cdoc,
3; =>  find-name #'user/find-name, help #'user/help,
4; =>  clojuredocs #'user/clojuredocs}
(ns-interns &#39;user) ; =&gt; {apropos-better #&#39;user/apropos-better, cdoc #&#39;user/cdoc, ; =&gt; find-name #&#39;user/find-name, help #&#39;user/help, ; =&gt; clojuredocs #&#39;user/clojuredocs}

Reading Var references, ns-refers

The function ns-refers takes a single argument, which should be a symbol in literal form specifying the name of a namespace or an object of that namespace, and returns a map containing mappings of symbolic identifiers to objects of type Var that were imported into the current namespace (e.g., using fn-refer).

Usage:

  • (ns-refers namespace).
Usage example for the ns-refers function
1(ns-refers 'user)
2; => {primitives-classnames #'clojure.core/primitives-classnames,
3; =>  +' #'clojure.core/+', …}
(ns-refers &#39;user) ; =&gt; {primitives-classnames #&#39;clojure.core/primitives-classnames, ; =&gt; +&#39; #&#39;clojure.core/+&#39;, …}

It is worth knowing that the original binding of a symbol to a Var in another namespace may be removed (e.g., using the ns-unmap function). In such a case, the binding reflected in the current namespace will not disappear, because the symbol will be mapped directly to the Var object, rather than to an entry in the internal map of another namespace. The downside of such a situation may, however, be a certain inconsistency in the metadata of the target object with the actual state: the metadata stored in the Var under the key :ns will point to the original namespace, in which we will no longer find the binding.

Reading class references, ns-imports

The function ns-imports takes a single argument, which should be a symbol in literal form specifying the name of a namespace or an object of that namespace, and returns a map containing mappings of symbolic identifiers to references pointing to Java classes. If the namespace does not exist, an exception will be thrown.

Usage:

  • (ns-imports namespace).
Usage example for the ns-imports function
1(ns-imports 'user)
2; => {Enum java.lang.Enum, InternalError java.lang.InternalError, …}
(ns-imports &#39;user) ; =&gt; {Enum java.lang.Enum, InternalError java.lang.InternalError, …}

Reading all mappings, ns-map

The function ns-map takes a single argument, which should be a symbol in literal form specifying the name of a namespace or an object of that namespace, and returns a map containing all mappings of symbolic identifiers to objects (reference variables of type Var and Java classes). If the namespace does not exist, an exception will be generated.

Usage:

  • (ns-map namespace).
Usage example for the ns-map function
1(ns-map 'user)
2; => {primitives-classnames #'clojure.core/primitives-classnames, … }
(ns-map &#39;user) ; =&gt; {primitives-classnames #&#39;clojure.core/primitives-classnames, … }

Reading public mappings, ns-publics

The function ns-publics takes a single argument, which should be a symbol in literal form specifying the name of a namespace or an object of that namespace, and returns a map containing public mappings of symbolic identifiers to objects. If the namespace does not exist, an exception will be generated.

Usage:

  • (ns-publics namespace).
Usage example for the ns-publics function
1(ns-publics 'user)
2; => {apropos-better #'user/apropos-better, cdoc #'user/cdoc,
3; =>  find-name #'user/find-name, help #'user/help, clojuredocs #'user/clojuredocs}
(ns-publics &#39;user) ; =&gt; {apropos-better #&#39;user/apropos-better, cdoc #&#39;user/cdoc, ; =&gt; find-name #&#39;user/find-name, help #&#39;user/help, clojuredocs #&#39;user/clojuredocs}

Library Handling

A library, or more precisely a software library, is a collection of resources placed in files that can be utilized by software to enrich the available functions. A library may contain data, subroutines (e.g., macros or functions), and even definitions of new data types. Thanks to libraries, it is possible to reuse already-implemented methods for solving problems.

Depending on the programming language, software libraries may consist exclusively of source code or may appear in compiled versions supplemented with source files containing declarations, thanks to which the compiler can link subroutine calls with the corresponding implementations in machine language or bytecode.

In Clojure, we will most commonly deal with source code libraries in Java archives (JARs) containing exclusively code written in Clojure. In some rare cases, we may encounter libraries that instead of source code in Clojure will contain files compiled to bytecode (.class files).

Loading Libraries

Loading software libraries and subsequently placing the needed references in the current namespace requires making use of several functions and macros presented earlier (e.g., refer or import). Fortunately, programmers do not have to toil too much, because there are macros that allow one to express concisely what should be loaded and what additional operations need to be performed on namespaces.

In Clojure, the files of a given library should be located in a directory placed on the classpath, and by convention, its name will be expressed as a symbol in literal form (when passing it to various macros or functions).

Standalone loading, load

The function load is responsible for loading libraries. It accepts zero or more arguments, which should be file system paths expressed as character strings.

Usage:

  • (load & path…).

For each given relative path (not beginning with a path name separator), the library file will be looked for in the root directory of the current namespace. The root directory is obtained by:

  • taking the name of the current namespace;
  • prepending a slash character (/);
  • replacing all hyphens (-) with underscore characters (_);
  • replacing all dots (.) with slash characters (/);
  • extracting the fragment from the beginning to the last occurrence of a slash;
  • appending a slash character at the end;
  • appending the path given as the argument at the end.

For each absolute path (beginning with a path name separator), a search will be performed across all locations that are combinations of successive paths placed on the classpath.

Examples of root directories depending on the namespace name and the given path:

  • current namespace user:

    • (load "test"): /test,
    • (load "one/two"): /one/two,
  • current namespace clojure.core:

    • (load "test"): /clojure/test,
    • (load "one/two"): /clojure/one/two,
    • (load "string"): /clojure/string.

Regardless of whether a relative or absolute path was given, the last element of the given path will be treated as the file name to load, and the text string with the .clj extension will be appended to it.

We can verify how the names are constructed by setting the dynamic variable *clojure.core/loading-verbosely* to a value other than false and other than nil within the dynamic scope of the binding macro.

Usage example for the load function
 1;; loading a project file
 2;; src/project/core.clj
 3
 4(load "project/core")
 5; => nil
 6
 7;; loading the root file
 8;; of the clojure.string library
 9
10(load "/clojure/string")
11; => nil
;; loading a project file ;; src/project/core.clj (load &#34;project/core&#34;) ; =&gt; nil ;; loading the root file ;; of the clojure.string library (load &#34;/clojure/string&#34;) ; =&gt; nil

The require macro

The require macro loads external software libraries. Each argument provided should be one of several clauses:

  • a library spec (library specification),
  • a prefix list,
  • a modifier flag.

A library spec is either a symbolically expressed library name, or a vector containing the name and additional parameters. The names of these parameters should be expressed as keywords and grouped in a sequential collection. Thanks to the library spec parameters, we can decide what happens right after the library is loaded into memory.

Possible parameters are:

  • :as symbolic-name – uses the alias function and creates a reference to the loaded library under the given name in the current namespace;

  • :refer (symbolic-names) – uses the refer function and for a sequence of symbolically expressed names creates references in the current namespace (providing the key :all means requesting the creation of references to all public global variables);

A prefix list makes it possible to load libraries whose names share the same beginning. This saves our keyboards and our fingers. Instead of the given common prefix, one creates a list of library specs. An important condition is that names on this list may no longer contain dots, i.e., they must be the last elements of the path (and name).

Modifier flags allow influencing the behavior of the macro. These are keywords:

  • :reload – forces re-loading of libraries into memory, even if they have already been loaded;

  • :reload-all – works like :reload, but affects all dependent libraries loaded by the one being read (if they make use of use or require);

  • :verbose – causes diagnostic information about loading and creating references to be printed.

The macro works in such a way that for each library being loaded, a namespace and a corresponding Java package are created – their names are constructed based on the given symbolic name. Loading a library is in essence reading its root file, located in the library’s root directory. The root file name is constructed according to the following scheme:

  • dots are replaced with path name separators (e.g., a.b becomes a/b);
  • the last part of the name is treated as the file name (e.g., b.clj);
  • the remaining part of the name is treated as the root directory name (e.g., a);
  • the relative path along with the file name is appended to successive classpath entries until the library’s root file is found.

The root file should define the namespace of the entire library.

If the library has already been loaded into memory previously, it will not be loaded again.

Usage:

  • (require & spec… & prefix-list… & flag…).
Usage examples for the require macro
 1;; will load the library and create the clojure.string namespace
 2
 3(require 'clojure.string)
 4; => nil
 5
 6;; we can specify multiple libraries
 7
 8(require 'clojure.string 'clojure.test 'clojure.set)
 9
10;; we can specify multiple libraries with a common prefix
11
12(require '[clojure string test set])
13
14;; will load the library and create the clojure.string namespace
15;; even if it was already loaded
16
17(require 'clojure.string :reload :verbose)
18; => (clojure.core/load "/clojure/string")
19; => nil
20
21;; will load the library if it has not been loaded yet
22;; and create an alias in the current namespace so that
23;; the clojure.string namespace is visible as st
24
25(require '[clojure.string :as st] :verbose)
26; => (clojure.core/in-ns 'user)
27; => (clojure.core/alias 'st 'clojure.string)
28; => nil
;; will load the library and create the clojure.string namespace (require &#39;clojure.string) ; =&gt; nil ;; we can specify multiple libraries (require &#39;clojure.string &#39;clojure.test &#39;clojure.set) ;; we can specify multiple libraries with a common prefix (require &#39;[clojure string test set]) ;; will load the library and create the clojure.string namespace ;; even if it was already loaded (require &#39;clojure.string :reload :verbose) ; =&gt; (clojure.core/load &#34;/clojure/string&#34;) ; =&gt; nil ;; will load the library if it has not been loaded yet ;; and create an alias in the current namespace so that ;; the clojure.string namespace is visible as st (require &#39;[clojure.string :as st] :verbose) ; =&gt; (clojure.core/in-ns &#39;user) ; =&gt; (clojure.core/alias &#39;st &#39;clojure.string) ; =&gt; nil

The use macro

The use macro works exactly like require and is invoked in the same way, but references to every global variable from the loaded library are automatically added to the current namespace, using the refer function.

The use macro can accept additional parameters in library specs:

  • :exclude symbol-sequence – symbols to be excluded,
  • :only symbol-sequence – symbols to be exclusively processed,
  • :rename symbol-map – symbols to be renamed.

Usage:

  • (use & spec… & prefix-list… & flag…).

Since Clojure release 1.4, it is recommended to use the require or ns macro with appropriate parameters instead of use.

The ns macro

The ns macro was created so that one would not have to invoke other macros and functions related to namespace handling, but rather group all important operations in one place (e.g., in the header section of a source code file).

The macro allows one to set the current namespace (create it if it does not yet exist and switch to it), and then load needed source code files, import all or selected mappings, and generate pseudocode for given classes.

The macro takes the name of the namespace that will be set as current, as well as an optional set of so-called reference clauses, which may contain commands for performing additional operations. The clauses should be grouped in a list S-expression containing keywords serving as their names. Arguments provided after keys do not need to be quoted – after evaluation they will be passed to the invoked functions or macros.

Optionally, after the namespace name, one may provide a documentation string (e.g., describing the source file), as well as an attribute map.

Usage:

  • (ns namespace-name docstring? attr-map? & clause…).

Reference clauses:

  • (:require …) – invokes require,
  • (:use …) – invokes use,
  • (:import …) – invokes import,
  • (:load …) – invokes load,
  • (:gen-class …) – invokes gen-class,
  • (:refer-clojure …) – invokes refer-clojure.

In the case of gen-class, the arguments passed to the invocation by default are:

  • :name namespace-name,
  • :impl-ns namespace-name,
  • :main true.

If AOT compilation is not taking place, the :gen-class clause is ignored. If this clause was not used but compilation is occurring, only the code for namespace-name__init.class will be produced.

Usage example for the ns macro
1(ns io.randomseed.examples
2  (:refer-clojure                         :exclude      [printf])
3  (:require [clojure.set                  :as    set           ]
4            [clojure.string               :as    string        ]
5            [clojure.repl                 :refer [doc dir]     ]
6            [io.randomseed.resources.file :as    files         ])
7  (:use     [io.randomseed.handy          :only  [a-func other]])
8  (:import  [java.util                           Date Random   ]))
(ns io.randomseed.examples (:refer-clojure :exclude [printf]) (:require [clojure.set :as set ] [clojure.string :as string ] [clojure.repl :refer [doc dir] ] [io.randomseed.resources.file :as files ]) (:use [io.randomseed.handy :only [a-func other]]) (:import [java.util Date Random ]))

In the example above, we can see that the namespace io.randomseed.examples is being created, and right after that, references to global variables from the language’s standard library are loaded, but excluding the object identified by the symbol printf.

Next, within the namespace io.randomseed.examples, aliases are created for the namespaces clojure.set, clojure.string, and io.randomseed.resources.file to make them easier to specify. In the same section, references to global variables from the clojure.repl namespace (including doc and dir) are also created so that they can be invoked without specifying the namespace.

The :use clause works similarly to :require with the :refer parameter, i.e., in the given namespace (here io.randomseed.handy) objects are located (here with the names a-fun and other) and in the namespace being handled by the macro (here io.randomseed.examples) references to them are created. It is recommended to use :require (with the :refer parameter) instead of :use.

The last clause (:import) creates references to Java classes (Date and Random) from the java.util package.

Handling Bindings

Thanks to bindings, we can identify objects placed in memory in Clojure. This identification will consist of:

Reading binding values is a task for the language mechanisms (it is enough to use an unquoted symbol in the source code), while in the case of reference types it is a task for the programmer, greatly aided by ready-made functions and reader macros. Below we will therefore focus on creating bindings depending on their types and the constructs used for this purpose.

In the case of binding forms we are dealing with bindings of symbolic identifiers to values. Their values cannot be updated, but it is possible to shadow them by binding a symbol of the given name to a different value in some context (e.g., lexical or dynamic).

In the case of reference types, we can update current values that instances of these types refer to, using appropriate functions. This way we can create stable identities that will refer to changing states.

Types of Bindings

Technically speaking, in Clojure we can encounter three main types of bindings:

  • symbol bindings to values,
  • reference object bindings to values,
  • dynamic Var bindings to values.

Dynamic Vars are handled by one of the reference types (Var) – the same one that is used to handle global Vars – however we distinguish them separately, because they are characterized by so-called dynamic scope.

Symbol Bindings

Symbol bindings serve to identify values or reference objects in certain contexts. We can distinguish symbol bindings:

  • in namespaces:

    • to global Vars (type Var, form def),
    • to Java classes (type java.lang.Class);
  • in lexical bindings:

    • to local values (forms let, loop and similar);
    • to local Var objects (form with-local-vars);
    • to function arguments in their definitions (so-called parameter bindings – forms fn, defn);

and additionally:

  • in abstract structural bindings:
    • to lexical bindings (form let and similar),
    • to function arguments (form fn and similar),
    • to macro arguments, which translate to the above-mentioned.

Semantically correct symbol bindings in certain contexts will also be called binding forms of symbols.

Structural Bindings

Structural bindings are lexical or parameter bindings in which destructuring of an associative structure (e.g., a map) or a sequential one (e.g., a vector) takes place, in order to bind multiple symbols to values at once.

Destructuring, which will be discussed later, can be imagined as a way of creating bindings using two similarly arranged structures. On the left side we place a structure containing unquoted symbols, and on the right a structure isomorphic to it with initializing values. Symbols placed in the left structure will be bound to values from the right structure depending on position (in the case of sequential collections, e.g., vectors) or keys (in the case of maps).

Reference Object Bindings

Reference type object bindings serve to track changing, shared states expressed by different values over time. The places where binding information is stored are reference objects, e.g.:

Dynamic Bindings

Dynamic bindings serve to temporarily shadow the values of global Vars that have the :dynamic flag set in their metadata. Such Vars are then called dynamic Vars. They differ from regular global Vars in the way the Var type object is initialized, and making use of a dynamic binding is realized using the binding form binding and constructs that make use of it.

Binding Scopes

Binding scope is the area of a program in which a given binding can be used.

Besides the scope of a binding we can also speak about the visibility of the identified value, that is, the area in which it is possible to refer to it. Visibility of a value depends on the binding scope, but it can also be additionally controlled using namespaces.

Visibility can be smaller than scope if in a given context the same symbol is used to denote more than one binding. We then speak of shadowing.

In Clojure we can encounter several kinds of scopes: indefinite, lexical, and dynamic.

Indefinite Scope

In the case of symbolically identified global Vars or Java classes, we will speak of indefinite scope, meaning the potential ability to refer to the indicated objects from any place in the program.

Thanks to this kind of scope we are able to express global, shared states in programs, which will be identified by constant names. Values may change over time, but the identities identifying them will remain constant throughout the program.

An example of widespread use of indefinite scope is function names. Symbolic identifiers are bound in namespaces to reference objects of type Var, which in turn contain references to function type objects. It is precisely thanks to namespaces that controlling visibility in this scope is possible.

Examples of defining and using global Vars
 1(ns nasza)        ; switch namespace
 2(def x 5)         ; global Var x (root binding with value 5)
 3(defn funk [] x)  ; function that returns the value of global Var x
 4(funk)            ; function call
 5; => 5            ; result of the call
 6
 7(ns inna)         ; switch namespace
 8(funk)            ; attempt to call the function
 9; >> Unable to resolve symbol: funk in this context
10
11(nasza/funk)      ; calling the function with a namespace-qualified symbol
12; => 5
(ns nasza) ; switch namespace (def x 5) ; global Var x (root binding with value 5) (defn funk [] x) ; function that returns the value of global Var x (funk) ; function call ; =&gt; 5 ; result of the call (ns inna) ; switch namespace (funk) ; attempt to call the function ; &gt;&gt; Unable to resolve symbol: funk in this context (nasza/funk) ; calling the function with a namespace-qualified symbol ; =&gt; 5

Let us note that after switching the current namespace to inna we lost visibility of the value bound to the global Var funk naming a function. The binding did not disappear, which is why by using a symbol form with a qualified namespace we can still make use of it.

Lexical Scope

We encounter lexical scope in the case of lexical bindings. The ability to use bindings covered by this scope depends on the placement of the symbols identifying them in the source code.

Lexical scope is used in many programming languages. We can then speak, for example, of local lexical scope (within a function body or a certain code block).

In Clojure, lexical scope:

  • is created explicitly using the special form let or similar ones;
  • is created automatically for function and macro parameters.
The let Form and the Binding Vector

Using the special form let, we can create lexical bindings whose scope will be limited to the S-expressions given as its arguments.

The let form is very commonly used in Clojure and in other Lisp dialects. One could say that alongside forms creating functions or lists it is one of the fundamental constructs of the language. Thanks to it we can write readable, declarative code and give values symbolic labels in selected areas of the program.

Usage:

  • (let binding-vector & expression...),

where binding-vector is:

  • [binding-form init-expression ...].

The first argument that should be passed to the let construct is the binding vector. It is a vector S-expression that should consist of so-called binding pairs. The first elements of these pairs should be binding forms, and the second ones so-called initialization expressions, which will be evaluated to constant values.

Binding forms in the binding vector of the let form can be expressed using:

Symbols should express binding forms, and thus appear in unquoted form, while maps or vectors are used in the case of so-called destructuring, which will be discussed later and allows creating abstract structural bindings. They find application when there is a need to bind symbols to values of specific elements originating from multi-element structures.

Example of using the let special form
1(let [a 1         ; binding symbol a to value 1
2      b (inc a)   ; binding symbol b to value a+1
3      c 3]        ; binding symbol c to value 3
4  (+ a b c))      ; bindings visible only in the let expression
5; => 6
(let [a 1 ; binding symbol a to value 1 b (inc a) ; binding symbol b to value a+1 c 3] ; binding symbol c to value 3 (+ a b c)) ; bindings visible only in the let expression ; =&gt; 6

Values assigned to binding forms can be represented by any S-expressions that will become valid forms. We call them initialization expressions in this context. In initialization expressions we can refer to symbols that were bound to values at earlier positions of the binding vector.

The subsequent, optional arguments of let are S-expressions to evaluate, in which we can use the previously bound symbols. When a given symbol is provided, its symbol form will be evaluated to a value.

It is worth noting that in the case of let we are not dealing with Var type objects, but with local bindings used to identify assigned values. Bindings of symbols to values created in the binding vector are stored on a special local binding stack, while new values arising as a result of evaluating initialization expressions occupy the heap space of the program.

We can shadow the values of lexical bindings by creating new ones that use the same symbolic names:

Example of shadowing lexical bindings
1(let [a 1         ; binding symbol a to value 1
2      b (inc a)   ; binding symbol b to the value bound to a + 1
3      a b]        ; binding symbol a to the value bound to b
4  a)
5; => 2
(let [a 1 ; binding symbol a to value 1 b (inc a) ; binding symbol b to the value bound to a + 1 a b] ; binding symbol a to the value bound to b a) ; =&gt; 2

The let form evaluates to the value of the last evaluated S-expression or to nil if no expression was provided.

The lexical scope of bindings created in the binding vector of the let form is limited to the initialization expressions of its vector and the S-expressions given as its arguments. The scope of each binding in the vector begins from the place of its creation – in the initialization expressions of the vector we can use bindings created at earlier positions.

Conditional Binding, if-let

The if-let macro works similarly to the let special form, internally making use of the if form. It allows creating one lexical binding visible in S-expressions that will be evaluated depending on whether the value from the initialization expression represents logical truth or falsehood.

The first argument of the if-let macro is a binding vector, the second should be an S-expression that will be evaluated if the value of the initialization expression in the vector is truthy (is not equal to false or nil). After it an optional third argument may appear, which will be evaluated if the value of the second turns out to be falsy (equal to false or nil). It is worth remembering that in this expression we cannot use the binding, because it will not be created.

The return value is the value of the last evaluated expression or nil if no expression was evaluated (because, e.g., the truth condition was not met and no additional expression to evaluate was provided).

Usage:

  • (if-let binding-vector truth-expression falsy-expression?)
Examples of using the if-let macro
1(if-let [a 1]     a)         ; => 1
2(if-let [a 0]     a)         ; => 0
3(if-let [a false] a)         ; => nil
4(if-let [a nil]   a)         ; => nil
5(if-let [a nil]   a "none")  ; => "none"
(if-let [a 1] a) ; =&gt; 1 (if-let [a 0] a) ; =&gt; 0 (if-let [a false] a) ; =&gt; nil (if-let [a nil] a) ; =&gt; nil (if-let [a nil] a &#34;none&#34;) ; =&gt; &#34;none&#34;

The lexical scope of the binding created in the binding vector of the if-let form is limited to the S-expressions given as its arguments.

Function Binding, letfn

The letfn macro is a version of the let special form that allows defining functions and creating their lexical bindings with symbols in such a way that they become visible in all initialization expressions of a given binding vector (even in those placed earlier).

In simple cases we can use let to bind a symbol to an anonymous function, and then call that function:

1(let [x (fn [a] (+ 2 a))]
2  (x 2))
3
4; => 4
(let [x (fn [a] (+ 2 a))] (x 2)) ; =&gt; 4

We can also call functions in the binding vector, during binding creation, and thus treat returned values as initialization expressions or their components:

1(let [x (fn [a] (+ 2 a))       ; function x
2      y (fn [a] (+ 3 (x a)))]  ; function y uses function x
3  (y 2))                       ; calling function y
4
5; => 7
(let [x (fn [a] (+ 2 a)) ; function x y (fn [a] (+ 3 (x a)))] ; function y uses function x (y 2)) ; calling function y ; =&gt; 7

Let us see, however, what happens when in the binding vector we refer to a function earlier than the binding of it to a symbol took place:

1(let [y (fn [a] (+ 3 (x a)))  ; function y uses function x
2      x (fn [a] (+ 2 a))]     ; function x
3  (y 2))                      ; calling function y
4
5; >> java.lang.RuntimeException:
6; >> Unable to resolve symbol: x in this context
(let [y (fn [a] (+ 3 (x a))) ; function y uses function x x (fn [a] (+ 2 a))] ; function x (y 2)) ; calling function y ; &gt;&gt; java.lang.RuntimeException: ; &gt;&gt; Unable to resolve symbol: x in this context

We can see that this is not possible, because the expressions of the binding vector are processed in a specific order. However, there are certain application domains where we need to refer to a function object that will only be defined later (e.g., in so-called mutual recursion). In such cases letfn comes to the rescue.

Usage:

  • (letfn function-spec-vector & expression...);

where function-spec-vector is:

  • [(name param-vector expression...)],
  • [(name (param-vector expression...)+)].

The second variant of the function specification vector is used to create so-called multi-arity functions, which are discussed in the chapter dedicated to functions.

Example of using the letfn macro
1(letfn [(y [a] (+ 3 (x a)))
2        (x [a] (+ 2 a))]
3  (y 2))
4
5; => 7
(letfn [(y [a] (+ 3 (x a))) (x [a] (+ 2 a))] (y 2)) ; =&gt; 7

The lexical scope of bindings created in the binding vector of the letfn form is limited to the initialization expressions of its vector and the S-expressions given as its arguments. The scope of each binding in the vector encompasses the entire vector – in the initialization expressions of the vector we can use any binding placed in it regardless of the order of creation.

See also:

Conditional Binding, when-let

The when-let macro is a version of the let special form that internally makes use of the when macro. It allows creating one lexical binding visible in expressions that will be evaluated provided that the value from the initialization expression represents logical truth (is not equal to false or nil).

The first argument of when-let should be a binding vector containing exactly one binding pair, and each subsequent one will be treated as an expression to be evaluated in which one can use the symbol form referring to the value bound in the vector.

The macro returns the value of the last evaluated expression or nil if no evaluation took place because the truth condition was not met.

Usage:

  • (when-let binding-vector & expression...).
Examples of using the when-let macro
1(when-let [a 0]     (str "got " a))  ; => "got 0"
2(when-let [a 1]     (str "got " a))  ; => "got 1"
3(when-let [a nil]   (str "got " a))  ; => nil
4(when-let [a false] (str "got " a))  ; => nil
(when-let [a 0] (str &#34;got &#34; a)) ; =&gt; &#34;got 0&#34; (when-let [a 1] (str &#34;got &#34; a)) ; =&gt; &#34;got 1&#34; (when-let [a nil] (str &#34;got &#34; a)) ; =&gt; nil (when-let [a false] (str &#34;got &#34; a)) ; =&gt; nil

The lexical scope of the binding created in the binding vector of the when-let form is limited to the S-expressions given as its arguments.

Binding of the 1st N-E, when-first

The when-first macro is a version of the when-let macro. It allows creating a lexical binding visible in expressions that will be evaluated provided that the value from the initialization expression is a structure that can be converted to a non-empty sequence.

The first argument of when-first should be a binding vector, containing exactly one binding pair, and each subsequent one will be treated as an expression to be evaluated in which one can use the symbol form referring to the value bound in the vector. The first element represented by the initialization expression will be bound.

The macro returns the value of the last evaluated expression or nil if no evaluation took place because the truth condition was not met.

Usage:

  • (when-first binding-vector & expression...).
Examples of using the when-first macro
1(when-first [a [0 1 2]]     (str "got " a))  ; => "got 0"
2(when-first [a [false 2 3]] (str "got " a))  ; => "got false"
3(when-first [a [nil 2 3]]   (str "got " a))  ; => "got "
4(when-first [a "123"]       (str "got " a))  ; => "got 1"
5(when-first [a nil]         (str "got " a))  ; => nil
6(when-first [a []]          (str "got " a))  ; => nil
7(when-first [a ""]          (str "got " a))  ; => nil
(when-first [a [0 1 2]] (str &#34;got &#34; a)) ; =&gt; &#34;got 0&#34; (when-first [a [false 2 3]] (str &#34;got &#34; a)) ; =&gt; &#34;got false&#34; (when-first [a [nil 2 3]] (str &#34;got &#34; a)) ; =&gt; &#34;got &#34; (when-first [a &#34;123&#34;] (str &#34;got &#34; a)) ; =&gt; &#34;got 1&#34; (when-first [a nil] (str &#34;got &#34; a)) ; =&gt; nil (when-first [a []] (str &#34;got &#34; a)) ; =&gt; nil (when-first [a &#34;&#34;] (str &#34;got &#34; a)) ; =&gt; nil

Note: The when-first macro calls the seq function on the value of the initialization expression (the second element of the binding pair) and errors may occur if such an operation is not possible (e.g., an integer or a boolean value was provided).

The lexical scope of the binding created in the binding vector of the when-first form is limited to the S-expressions given as its arguments.

Binding of Non-Nil Values, when-some

The when-some macro is a version of the let special form that internally makes use of the when macro. It allows creating one lexical binding visible in expressions that will be evaluated provided that the value from the initialization expression is different from nil.

The first argument of when-some should be a binding vector, containing exactly one binding pair, and each subsequent one will be treated as an expression to be evaluated in which one can use the symbol form referring to the value bound in the vector.

The macro returns the value of the last evaluated expression or nil if no evaluation took place because the condition was not met.

Usage:

  • (when-some binding-vector & expression...).
Examples of using the when-some macro
1(when-some [a 0]     (str "got " a))  ; => "got 0"
2(when-some [a 1]     (str "got " a))  ; => "got 1"
3(when-some [a false] (str "got " a))  ; => "got false"
4(when-some [a nil]   (str "got " a))  ; => nil
(when-some [a 0] (str &#34;got &#34; a)) ; =&gt; &#34;got 0&#34; (when-some [a 1] (str &#34;got &#34; a)) ; =&gt; &#34;got 1&#34; (when-some [a false] (str &#34;got &#34; a)) ; =&gt; &#34;got false&#34; (when-some [a nil] (str &#34;got &#34; a)) ; =&gt; nil

The lexical scope of the binding created in the binding vector of the when-some form is limited to the S-expressions given as its arguments.

Binding of Non-Nil Values, if-some

The if-some macro is a version of the let special form that internally makes use of the if special form. It allows creating one lexical binding visible in an expression that will be evaluated provided that the value from the initialization expression is different from nil. Optionally, a second expression can also be provided, which will be evaluated otherwise.

The first argument of if-some should be a binding vector, containing exactly one binding pair, and the next (also mandatory) one will be treated as an expression to be evaluated in which one can use the symbol form referring to the value bound in the vector, if the initialization expression does not have the value nil. The optional, third argument should contain a second expression that will be executed when the value of the initialization expression is nil. It is worth remembering that the binding will not be visible in it, because it will not be created.

The macro returns the value of the last evaluated expression or nil if no evaluation took place because the condition was not met.

Usage:

  • (if-some binding-vector non-nil-expression & nil-expression).
Examples of using the if-some macro
1(if-some [a 0]     (str "got " a))         ; => "got 0"
2(if-some [a 1]     (str "got " a))         ; => "got 1"
3(if-some [a false] (str "got " a))         ; => "got false"
4(if-some [a nil]   (str "got " a))         ; => nil
5(if-some [a nil]   (str "got " a) "none")  ; => "none"
(if-some [a 0] (str &#34;got &#34; a)) ; =&gt; &#34;got 0&#34; (if-some [a 1] (str &#34;got &#34; a)) ; =&gt; &#34;got 1&#34; (if-some [a false] (str &#34;got &#34; a)) ; =&gt; &#34;got false&#34; (if-some [a nil] (str &#34;got &#34; a)) ; =&gt; nil (if-some [a nil] (str &#34;got &#34; a) &#34;none&#34;) ; =&gt; &#34;none&#34;

The lexical scope of the binding created in the binding vector of the if-some form is limited to the S-expressions given as its arguments.

Binding in a Loop, loop and recur

The loop special form works similarly to let, but allows recursive execution of a program fragment. It accepts one mandatory argument, which should be a binding vector and zero or more arguments that are expressions in which one can use the lexical bindings created in the vector. The return value is the value of the last evaluated expression.

Bindings used in expressions inside loop can be updated in the recur call. Arguments passed to recur will become the new binding values at their corresponding positions in the vector during the next recursive invocation of the expressions from loop. This makes so-called tail recursion possible, which does not exhaust stack memory resources.

Usage:

  • (loop binding-vector & expression...).
Example of using the loop special form
1(loop [x 1]            ; loop and lexical binding
2  (when (< x 10)       ; recursion termination condition
3    (println x)        ; display; binding visible only in the loop
4    (recur (inc x))))  ; change binding of x and jump to the beginning
(loop [x 1] ; loop and lexical binding (when (&lt; x 10) ; recursion termination condition (println x) ; display; binding visible only in the loop (recur (inc x)))) ; change binding of x and jump to the beginning

The lexical scope of bindings created in the binding vector of the loop form is limited to the initialization expressions of its vector (in the order of their occurrence) and the S-expressions given as its arguments. The scope of each binding in the vector begins from the place of its creation – in the initialization expressions of the vector we can use bindings created at earlier positions.

See also:

Parameter Binding

We will also encounter lexical scope when we define a function that accepts some arguments. We then speak of parameter bindings, that is, creating binding forms of symbols in parameter vectors of functions.

Example of using function parameters
1(defn funk [a b]  ; definition of function funk; parameters a and b
2  (+ a b))        ; visible only in the function body (within the S-expression)
3
4(fn [a b]         ; definition of an anonymous function; parameters a and b
5  (+ a b))        ; visible only in the function body (within the S-expression)
(defn funk [a b] ; definition of function funk; parameters a and b (+ a b)) ; visible only in the function body (within the S-expression) (fn [a b] ; definition of an anonymous function; parameters a and b (+ a b)) ; visible only in the function body (within the S-expression)

The lexical scope of parameter bindings to values passed as arguments during calls is limited to the function body.

Local Vars, with-local-vars

As an exception, we can give Var type objects local lexical scope. We will use this kind of construct when we want to express some problem imperatively and consequently there is a need to use an equivalent of local variables. The with-local-vars macro serves this purpose and is discussed in more detail in the chapter dedicated to Var type objects and variables.

Usage:

  • (with-local-vars binding-vector expression)
Example of using the with-local-vars macro
1(with-local-vars [a 1] @a)
2; => 1
(with-local-vars [a 1] @a) ; =&gt; 1

The lexical scope of bindings created in the binding vector of the with-local-vars form is limited to the S-expressions given as its arguments. An attempt to refer in an initialization expression of the vector to a variable bound to a symbol at an earlier position of the same vector will result in an exception being thrown.

See also:

I/O Binding, with-open

The with-open macro allows creating lexical scope for bindings in a similar way to let. Lexical bindings will also be created, whose scope will be limited to the S-expressions given as passed arguments, but all initialization values of the bindings after evaluation must be Java objects for which the close() method can be called.

Usage:

  • (with-open binding-vector & expression...),

where binding-vector is:

  • [symbol-binding-form init-expression ...].

The first argument that should be passed to the with-open construct is the binding vector. It is a vector S-expression that should consist of so-called binding pairs.

The first elements of these pairs should be symbol binding forms (and not, as in the case of let, arbitrary binding forms), and the second ones so-called initialization expressions, which will be evaluated to constant values.

Example of using the with-open macro
1(with-open [reader (clojure.java.io/reader "file.txt")]
2  (doall (line-seq reader)))
(with-open [reader (clojure.java.io/reader &#34;file.txt&#34;)] (doall (line-seq reader)))

In initialization expressions we can refer to symbols that were bound to values at earlier positions of the binding vector.

The subsequent, optional arguments of with-open are S-expressions to evaluate, in which we can use the previously bound symbols. When a given symbol is provided, its symbol form will be evaluated to a value.

Similarly to the let form, we are dealing with local bindings used to identify assigned values. All these values must be Java objects for which the close() method can be called. The macro will call it in a finally block, and thus regardless of whether the evaluation of expressions succeeds or not. Thanks to this, any open file handles, sockets, or perhaps even database connection objects will be closed after the evaluation of the expressions passed to the macro.

Dynamic Scope

Dynamic scope in Clojure is a scope in which we are dealing with shadowing of values of existing global Var bindings (of indefinite scope) by maintaining a global binding stack for each of them. This happens independently of the lexical context and requires the use of a special form.

The binding stack is a structure whose task is to handle shadowing of the current global Var’s value in a certain execution context limited by time.

If there is a global Var for which at some point a dynamic scope is created by the programmer, then on the stack assigned to this Var a new binding with a value is placed. It will be removed from the stack only when the evaluation of the expression in which the dynamic binding was established is completed (in the case of Clojure, the body of the binding macro, which will be discussed further).

If during program execution in which we have a global Var with dynamic scope another shadowing of it appears (caused by the introduction of a new dynamic scope), the binding again goes onto the stack associated with the given Var.

Every reference to a global Var for which a non-empty stack of dynamic bindings exists results in returning the value that the last (most recent) binding on that stack refers to. This happens independently of the lexical context. We can therefore call a dynamic binding one that lasts for a certain time, as opposed to lexical bindings, which cover certain areas of source code.

When there is no dynamic binding on the stack, the so-called root binding of the global Var is used.

Dynamic bindings of global Vars are realized by shadowing values in the bindings of Var type reference objects representing those Vars, and not by shadowing symbol mappings in namespaces. Furthermore, dynamic shadowings of global Vars are always performed in the current thread of execution. If in other threads no dynamic binding was created (using the binding construct), the Var will retain its current root binding with a value in them.

Creating Dynamic Bindings

The binding macro, which is discussed in more detail in chapter VII, is used to create bindings with dynamic scope. Below we will find a usage example that also demonstrates that bindings of this type are maintained in reference objects, not in namespaces:

Example of using dynamic bindings
 1(def ^:dynamic *x* 5)                   ; dynamic Var *x*
 2
 3(def obj-x                              ; global Var obj-x
 4  (var *x*))                            ; points to the Var object of *x*
 5
 6(defn show-off []                       ; displays the value of *x*
 7  (println " *x* by name:"
 8           *x* "\n"                     ; reading the bound value
 9           "*x* by object:"
10           @obj-x "\n"))                ; dereferencing the object
11
12(defn test-it []                        ; test function
13  (binding [*x* 10]                     ; dynamic scope of *x*
14    (println "* dynamic scope")
15    (show-off))                         ; calling function within the scope
16  (println "* indefinite scope:")       ; dynamic
17  (show-off))                           ; calling function outside
18                                        ; the dynamic scope
19(test-it)
(def ^:dynamic *x* 5) ; dynamic Var *x* (def obj-x ; global Var obj-x (var *x*)) ; points to the Var object of *x* (defn show-off [] ; displays the value of *x* (println &#34; *x* by name:&#34; *x* &#34;\n&#34; ; reading the bound value &#34;*x* by object:&#34; @obj-x &#34;\n&#34;)) ; dereferencing the object (defn test-it [] ; test function (binding [*x* 10] ; dynamic scope of *x* (println &#34;* dynamic scope&#34;) (show-off)) ; calling function within the scope (println &#34;* indefinite scope:&#34;) ; dynamic (show-off)) ; calling function outside ; the dynamic scope (test-it)

See also:

Destructuring

Destructuring (also called decomposition) is a mechanism for creating bindings of values to symbols, where those values come from structures composed of multiple elements, and specific syntax is used to assign selected values to particular symbols instead of calling functions or macros.

We can use destructuring in the binding vector of the let special form and its derivatives, in the parameter vector of the fn form and the defn macro, as well as in constructs that make use of the aforementioned (e.g., for or doall). Instead of symbol binding forms, the first elements of each binding pair will then be vector binding forms, map binding forms, or even combinations thereof.

To demonstrate the benefits of using destructuring, let us look at a simple example in which we first bind elements of a vector to symbols (using functions that operate on the vector), and then use destructuring for the same purpose.

Comparison of destructuring and manual value binding
 1(def data [1 2 3])  ; vector with three values
 2
 3;; manual binding of selected elements to symbols
 4
 5(let [a (first data)
 6      b (first (rest data))
 7      c (first (rest (rest data)))]
 8  (list a b c))
 9; => (1 2 3)
10
11;; destructuring
12
13(let [[a b c] data] (list a b c))
14; => (1 2 3)
(def data [1 2 3]) ; vector with three values ;; manual binding of selected elements to symbols (let [a (first data) b (first (rest data)) c (first (rest (rest data)))] (list a b c)) ; =&gt; (1 2 3) ;; destructuring (let [[a b c] data] (list a b c)) ; =&gt; (1 2 3)

In the second-to-last line we can see that instead of a single symbol, we used a vector S-expression containing a list of symbols that were bound to the positionally corresponding values of the vector named data.

Destructuring is a kind of binding of symbols to values. We are still dealing with binding pairs, however:

  • in the place of a single symbol there appears a binding expression containing various symbols (vector binding expression or map binding expression), i.e., a vector or map binding form;

  • the value of the assigned initializing expression is a multi-element structure (e.g., a vector, map, list, record, etc.).

Names of binding expressions and forms in a let invocation example
1(let [         ; binding vector:
2               ; - first pair:
3      data     ;   - symbol (symbol binding form)
4      [1 2 3]  ;   - initializing expression (vector form)
5               ; - second pair:
6      [a b c]  ;   - vector binding expression (vector binding form)
7      data]    ;   - initializing expression (symbol form)
8  (list a b c))
(let [ ; binding vector: ; - first pair: data ; - symbol (symbol binding form) [1 2 3] ; - initializing expression (vector form) ; - second pair: [a b c] ; - vector binding expression (vector binding form) data] ; - initializing expression (symbol form) (list a b c))

Positional Destructuring

Positional destructuring (also called positional decomposition) enables creating bindings of symbols to values of selected elements of structures with a sequential access interface (e.g., vectors, lists, or even character strings). It resembles the use of pattern matching and consists of associating symbols given in a certain order with the positionally corresponding elements of the structure provided in the initializing expression.

Positional destructuring can be used both in binding vectors of special forms (such as let or binding), as well as in parameter vectors of function definitions.

In fact, we can destructure not only sequences but any collections on which the nth function can operate.

Vector Binding Form

Using positional destructuring requires placing a vector binding expression in the position where we normally provide a single symbol (as the first element of a binding pair). This should be a vector S-expression containing binding forms (e.g., unquoted symbols) whose positions correspond to the positions of elements in the source structure (given as the initializing expression).

Usage:

  • [[symbol...] initializing-expression].
Examples of positional destructuring in a let construct
 1(let [[a b c] [1 2 3]]        ; a -> 1, b -> 2, c -> 3
 2  (list a b c))
 3; => (1 2 3)
 4
 5(let
 6  [vector     [4 5 6]      ; binding to a vector
 7  sequence  (seq vector)    ; binding to a sequence based on the vector
 8  [a b c]   [1 2 3]         ; bindings from destructuring a vector expression
 9  [d e f]   vector           ; bindings from destructuring the vector
10  [g    ]   sequence]        ; bindings from destructuring the sequence
11
12  (list a b c d e f g))     ; creating a list with the binding values
13; => (1 2 3 4 5 6 4)
(let [[a b c] [1 2 3]] ; a -&gt; 1, b -&gt; 2, c -&gt; 3 (list a b c)) ; =&gt; (1 2 3) (let [vector [4 5 6] ; binding to a vector sequence (seq vector) ; binding to a sequence based on the vector [a b c] [1 2 3] ; bindings from destructuring a vector expression [d e f] vector ; bindings from destructuring the vector [g ] sequence] ; bindings from destructuring the sequence (list a b c d e f g)) ; creating a list with the binding values ; =&gt; (1 2 3 4 5 6 4)

In the case of parameter vectors, which we encounter e.g. in function definitions, the initializing expression will be the set of passed arguments.

Example of a parameter vector in a function definition
1(defn a-function [a b c]
2  (list a b c))
3
4(a-function 1 2 3)
5; => (1 2 3)
(defn a-function [a b c] (list a b c)) (a-function 1 2 3) ; =&gt; (1 2 3)

Ignoring Elements

Note that in line 9 of the previous example we provide only one symbol (g), while the source sequence contains three values (4, 5, 6). As expected, the first of them will be bound to the symbol. But is there a way to retrieve only a selected one while ignoring the rest? The _ symbol comes to the rescue here, indicating that the element at its corresponding position should be ignored.

Usage:

  • [[... _...] initializing-expression].
Example of using the _ symbol in positional destructuring
1(let [[_ b _] [1 2 3]]  ; bindings from destructuring
2  b)                     ; evaluating the symbol form
3; => 2
(let [[_ b _] [1 2 3]] ; bindings from destructuring b) ; evaluating the symbol form ; =&gt; 2

It is worth knowing that the _ symbol has special meaning only by convention. Any other symbol that will not be used can be placed in its stead, and its value can be overwritten multiple times without affecting the application logic.

Grouping Elements

An interesting case is grouping all remaining, positionally unassigned values into a single, variadic binding. The ampersand symbol placed before the symbol name serves this purpose.

Usage:

  • [[... & symbol] initializing-expression].
Example of using the & symbol in positional destructuring
1(let [[_ & remaining] [1 2 3]]  ; bindings from destructuring
2  remaining)                     ; evaluating the symbol form
3; => (2 3)
(let [[_ &amp; remaining] [1 2 3]] ; bindings from destructuring remaining) ; evaluating the symbol form ; =&gt; (2 3)

Accessing the Original Sequence

It may happen that despite destructuring we still need access to the originally passed data structure of the initializing expression. The :as directive comes to the rescue here, which should be placed inside the destructuring expression. Immediately after it there should be an unquoted symbol to which the structure should be bound.

Usage:

  • [[... :as symbol] initializing-expression].
Example of using the :as directive in positional destructuring
1(let [[a b c :as everything] [1 2 3]] ; bindings from destructuring
2  everything)                          ; evaluating the symbol form
3; => [1 2 3]
(let [[a b c :as everything] [1 2 3]] ; bindings from destructuring everything) ; evaluating the symbol form ; =&gt; [1 2 3]

The :as directive and the symbol assigned to it should be given as the last pair in the destructuring vector.

Associative Destructuring

Associative destructuring (also called associative decomposition) enables creating bindings of symbols to values originating from selected elements of structures with an associative access interface (e.g., maps or records).

Associative destructuring can be used both in binding vectors (e.g., of the let special form or the binding macro), as well as in parameter vectors of function definitions.

Map Binding Form

Associative structures (e.g., maps) express a key-value relationship, and their destructuring consists of specifying keys under which one can find values to be bound to the given symbols.

A map binding expression, which can be called a binding map for short, serves to express this operation. It is a map S-expression that should be placed as the first element of each binding pair in the binding vector, or in place of a single parameter in a function’s parameter vector. The keys of the map can be binding forms (e.g., of symbols, maps, or vectors), and the values are keys of the source structure under which we will find the actual initializing values or further structures.

The source structure from which values will be retrieved for binding to symbols or further destructuring will be the map initializing expression given as the second element of each binding pair.

Usage:

  • [{symbol key ...} initializing-expression].
Example of using a binding map
1;;          B I N D I N G   V E C T O R    (consisting of pairs)
2;;         binding map     initializing expression  (binding pairs)
3
4(let [ {a :a b :b c :c}    {:a 1, :b 2, :c 3}  ]
5  (list a b c))
6; => (1 2 3)
;; B I N D I N G V E C T O R (consisting of pairs) ;; binding map initializing expression (binding pairs) (let [ {a :a b :b c :c} {:a 1, :b 2, :c 3} ] (list a b c)) ; =&gt; (1 2 3)

Key specifiers can also be other values, not only keywords.

Example of using a binding map with symbols as key specifiers
1(let [{a 'a b 'b c 'c} '{a 1, b 2, c 3}]
2  (list a b c))
3; => (1 2 3)
(let [{a &#39;a b &#39;b c &#39;c} &#39;{a 1, b 2, c 3}] (list a b c)) ; =&gt; (1 2 3)

Note that in the example above we quoted the map S-expression so that we would not have to separately quote each symbol within it.

In the case of function parameter vectors, the initializing expression comes from the arguments passed to the function, and the keys are their symbolic names.

Example of using a binding map in a parameter vector
 1;;              PARAMETER   VECTOR
 2;;                binding   map
 3
 4(defn a-function [ & {a :a, b :b, c :c} ]
 5  (list a b c))
 6
 7;;        arguments (initializing expression)
 8
 9(a-function          :a 1, :b 2, :c 3)
10; => (1 2 3)
;; PARAMETER VECTOR ;; binding map (defn a-function [ &amp; {a :a, b :b, c :c} ] (list a b c)) ;; arguments (initializing expression) (a-function :a 1, :b 2, :c 3) ; =&gt; (1 2 3)

Binding Map Keys

If the names of the symbols to which values from the given associative structure will be bound are to be the same as the key names in that map, we can use the :keys directive. It allows avoiding repetition and makes the code more readable.

In the binding map, one should provide a pair whose key is the keyword :keys, and whose assigned value is a vector S-expression containing unquoted symbols or keywords designating the keys of the decomposed structure whose values we want to bind to symbols with the same names.

Usage:

  • [{:keys [key...]} initializing-expression].
Examples of using the :keys directive
1(let [{:keys [:a :b :c]} {:a 1, :b 2, :c 3}]
2  (list a b c))
3; => (1 2 3)
4
5(let [{:keys [a b c]} {:a 1, :b 2, :c 3}]
6  (list a b c))
7; => (1 2 3)
(let [{:keys [:a :b :c]} {:a 1, :b 2, :c 3}] (list a b c)) ; =&gt; (1 2 3) (let [{:keys [a b c]} {:a 1, :b 2, :c 3}] (list a b c)) ; =&gt; (1 2 3)

The keys of the decomposed initializing structure can also be strings or symbols. In such cases, one can use the :strs or :syms directive instead of :keys. In both cases, unquoted symbols should be used to specify the names.

Usage:

  • [{:strs [key...]} initializing-expression],
  • [{:syms [key...]} initializing-expression].
Examples of using the :strs and :syms directives
1(let [{:strs [a b c]} {"a" 1, "b" 2, "c" 3}]
2  (list a b c))
3; => (1 2 3)
4
5(let [{:syms [a b c]} '{a 1, b 2, c 3}]
6  (list a b c))
7; => (1 2 3)
(let [{:strs [a b c]} {&#34;a&#34; 1, &#34;b&#34; 2, &#34;c&#34; 3}] (list a b c)) ; =&gt; (1 2 3) (let [{:syms [a b c]} &#39;{a 1, b 2, c 3}] (list a b c)) ; =&gt; (1 2 3)

Accessing the Original Association

It may happen that despite destructuring we still need access to the originally passed data structure. Similarly to positional destructuring, the :as directive comes to the rescue. It should be placed in the binding map, and the value assigned to it must be an unquoted symbol to which the structure of the initializing expression will be bound.

Usage:

  • [{:as symbol} initializing-expression].
Example of using the :as directive in associative destructuring
1(let [{:keys [a b c]
2       :as everything} [1 2 3]] ; bindings from destructuring
3  everything)                   ; evaluating the symbol form
4; => [1 2 3]
(let [{:keys [a b c] :as everything} [1 2 3]] ; bindings from destructuring everything) ; evaluating the symbol form ; =&gt; [1 2 3]

The :as directive and the symbol assigned to it should be given as the last pair in the destructuring vector.

Default Values

In the binding map we can specify default values that will be bound to symbols if the given keys were not found in the source structure. The :or directive serves this purpose.

After the keyword :or, a map specifying the default values for keys should be provided.

Usage:

  • [{:or {key value ...}} initializing-expression].
Example of using the :or directive
1(let [{:keys [:a :b :c]
2       :or {:a 1, :c 3}} {:b 2}]
3  (list a b c))
4; => (1 2 3)
(let [{:keys [:a :b :c] :or {:a 1, :c 3}} {:b 2}] (list a b c)) ; =&gt; (1 2 3)

Associative Destructuring of Vectors

It is possible to apply associative destructuring to vectors. In the binding map, instead of keys, one should then provide the positions of elements in the source sequential structure expressed as integers.

Usage:

  • [{symbol position ...} initializing-expression].
Example of associative destructuring of a vector
1(let [{a 0 b 1 c 2} ["first" "second" "third"]]
2  (list a b c))
3; => ("first" "second" "third")
(let [{a 0 b 1 c 2} [&#34;first&#34; &#34;second&#34; &#34;third&#34;]] (list a b c)) ; =&gt; (&#34;first&#34; &#34;second&#34; &#34;third&#34;)

Vectors as Keys in Destructuring

An interesting example of associative destructuring is a binding map in which the keys are vectors.

Example of associative destructuring with vector keys
1(let [{[a b c] :letters} {:letters [1 2 3]}]
2  (list a b c))
3; => (1 2 3)
(let [{[a b c] :letters} {:letters [1 2 3]}] (list a b c)) ; =&gt; (1 2 3)

We can see that the value of the vector key in the map binding form is a keyword (:letters), which will then be looked up in the map initializing expression, and positional destructuring will be performed on the found value. This kind of destructuring is a simple example of the ability to use nested structures in destructuring forms.

Nested Structures

Destructuring of nested structures is possible thanks to syntax that allows nesting map and vector binding expressions.

Example of destructuring a nested structure
 1(def personal-data
 2  {:first-name "Paul"
 3   :last-name  "Wilk"
 4   :sex        :m
 5   :contacts   {:phones  [123456, 543210]
 6                :emails  ["pw-at-gnu.org"]}})
 7
 8(let [{:keys [first-name last-name sex], {[phone] :phones
 9                                           [email] :emails} :contacts}
10      personal-data
11      sex-name (if (= sex :m) "male" "female")]
12  (println "Full name: " first-name last-name)
13  (println "Sex:       "           sex-name)
14  (println "Phone:     "              phone)
15  (println "Email:     "              email))
(def personal-data {:first-name &#34;Paul&#34; :last-name &#34;Wilk&#34; :sex :m :contacts {:phones [123456, 543210] :emails [&#34;pw-at-gnu.org&#34;]}}) (let [{:keys [first-name last-name sex], {[phone] :phones [email] :emails} :contacts} personal-data sex-name (if (= sex :m) &#34;male&#34; &#34;female&#34;)] (println &#34;Full name: &#34; first-name last-name) (println &#34;Sex: &#34; sex-name) (println &#34;Phone: &#34; phone) (println &#34;Email: &#34; email))

The result of running the above example will be the display of the following lines of text:

Full name:  Paul Wilk
Sex:        male
Phone:		 123456
Email:		 pw-at-gnu.org

Let us examine the individual fragments of the binding vector to better understand the operations involved. It contains two binding pairs:

  • A binding map in which destructuring takes place and its assigned initializing expression, which is a symbol form (personal-data) representing a nested map with personal data:
1{:keys [first-name last-name sex], {[phone] :phones
2                                    [email] :emails} :contacts}
3personal-data
{:keys [first-name last-name sex], {[phone] :phones [email] :emails} :contacts} personal-data
  • A binding form of a symbol (sex-name) and its assigned initializing expression, which is the if special form (depending on the value bound to the sex symbol, it emits the appropriate text string):
1sex-name (if (= sex :m) "male" "female")
sex-name (if (= sex :m) &#34;male&#34; &#34;female&#34;)

The second pair is not relevant to destructuring, so we will not discuss it further. Let us instead take a closer look at the first pair, where we have a binding map composed of two elements (two key-value pairs):

  • The :keys directive binding values of the keys :first-name, :last-name, and :sex to the corresponding symbols (from the map identified by the symbol personal-data):
1:keys [first-name last-name sex]
:keys [first-name last-name sex]
  • A binding map that destructures the structure identified by the key :contacts from the personal-data map:
1{[phone] :phones
2 [email] :emails}
3:contacts
{[phone] :phones [email] :emails} :contacts

We can see that the binding map does not contain simple binding forms (expressed as unquoted symbols), but rather two vector binding expressions, which represent another level of destructuring. We are dealing with positional destructuring, specifically with binding the symbol phone to the first element of the structure identified by the key :phones and the symbol email to the first element of the structure identified by the key :emails. Both of these structures (a vector containing phone numbers and a vector containing email addresses) should be elements of the map identified by the key :contacts in the parent structure.

Fully Qualified Keys

Since Clojure version 1.6, we can use keys and symbols with specified namespaces.

Examples of destructuring with namespace-qualified keys
 1(def personal-data
 2  {:first-name "Paul"
 3   :contacts/phones [123456, 543210]
 4   :contacts/emails ["pw-at-gnu.org"]})
 5
 6(let [{:keys [first-name],
 7       [phone] :contacts/phones,
 8       [email] :contacts/emails} personal-data]
 9  (println "Name:  " first-name)
10  (println "Phone:"     phone)
11  (println "Email: "    email))
12
13(let [{:keys [first-name contacts/phones contacts/emails]} personal-data]
14  (println "Name:   "     first-name)
15  (println "Phones:"        phones)
16  (println "Emails: "       emails))
(def personal-data {:first-name &#34;Paul&#34; :contacts/phones [123456, 543210] :contacts/emails [&#34;pw-at-gnu.org&#34;]}) (let [{:keys [first-name], [phone] :contacts/phones, [email] :contacts/emails} personal-data] (println &#34;Name: &#34; first-name) (println &#34;Phone:&#34; phone) (println &#34;Email: &#34; email)) (let [{:keys [first-name contacts/phones contacts/emails]} personal-data] (println &#34;Name: &#34; first-name) (println &#34;Phones:&#34; phones) (println &#34;Emails: &#34; emails))
Example of destructuring with keys from the current namespace
 1(ns user)
 2(def personal-data
 3  {:first-name "Paul"
 4   ::phones [123456, 543210]
 5   ::emails ["pw-at-gnu.org"]})
 6
 7(let [{:keys [first-name ::phones user/emails]} personal-data]
 8  (println "Name:   "     first-name)
 9  (println "Phones:"        phones)
10  (println "Emails: "       emails))
(ns user) (def personal-data {:first-name &#34;Paul&#34; ::phones [123456, 543210] ::emails [&#34;pw-at-gnu.org&#34;]}) (let [{:keys [first-name ::phones user/emails]} personal-data] (println &#34;Name: &#34; first-name) (println &#34;Phones:&#34; phones) (println &#34;Emails: &#34; emails))
Example of destructuring with namespace-qualified symbols
 1(def personal-data
 2  {'first-name "Paul"
 3   'contacts/phones [123456, 543210]
 4   'contacts/emails ["pw-at-gnu.org"]})
 5
 6(let [{:syms [first-name],
 7       [phone] 'contacts/phones,
 8       [email] 'contacts/emails} personal-data]
 9  (println "Name:  " first-name)
10  (println "Phone:"     phone)
11  (println "Email: "    email))
(def personal-data {&#39;first-name &#34;Paul&#34; &#39;contacts/phones [123456, 543210] &#39;contacts/emails [&#34;pw-at-gnu.org&#34;]}) (let [{:syms [first-name], [phone] &#39;contacts/phones, [email] &#39;contacts/emails} personal-data] (println &#34;Name: &#34; first-name) (println &#34;Phone:&#34; phone) (println &#34;Email: &#34; email))

In the above example, symbols with specified namespaces were quoted in the binding map, because otherwise they would be treated as symbol forms.

Diagnosing Destructuring

Destructuring complex data collections can carry a risk of error. In such cases it is worth using methods that enable inspecting the destructuring process.

Destructuring to Text, destructure

Thanks to the destructure function we can observe what effect the destructuring of given structures will have.

Usage:

  • (destructure bindings).

The function takes one required argument, which should be a binding vector in literal form.

The returned value is a binding vector containing representations of S-expressions used in the destructuring process (literal forms).

Example of using the destructure function
 1(def personal-data
 2  {'first-name "Paul"
 3   'contacts/phones [123456, 543210]
 4   'contacts/emails ["pw-at-gnu.org"]})
 5
 6(destructure '[{:syms
 7                [first-name],
 8                [phone] 'contacts/phones,
 9                [email] 'contacts/emails} personal-data])
10
11; => [map__10728
12; =>  personal-data
13; =>  map__10728
14; =>  (if
15; =>   (clojure.core/seq? map__10728)
16; =>   (clojure.lang.PersistentHashMap/create
17; =>     (clojure.core/seq map__10728))
18; =>   map__10728)
19; =>  vec__10729
20; =>  (clojure.core/get map__10728 (quote contacts/phones))
21; =>  phone
22; =>  (clojure.core/nth vec__10729 0 nil)
23; =>  vec__10730
24; =>  (clojure.core/get map__10728 (quote contacts/emails))
25; =>  email
26; =>  (clojure.core/nth vec__10730 0 nil)
27; =>  first-name
28; =>  (clojure.core/get map__10728 (quote first-name))]
(def personal-data {&#39;first-name &#34;Paul&#34; &#39;contacts/phones [123456, 543210] &#39;contacts/emails [&#34;pw-at-gnu.org&#34;]}) (destructure &#39;[{:syms [first-name], [phone] &#39;contacts/phones, [email] &#39;contacts/emails} personal-data]) ; =&gt; [map__10728 ; =&gt; personal-data ; =&gt; map__10728 ; =&gt; (if ; =&gt; (clojure.core/seq? map__10728) ; =&gt; (clojure.lang.PersistentHashMap/create ; =&gt; (clojure.core/seq map__10728)) ; =&gt; map__10728) ; =&gt; vec__10729 ; =&gt; (clojure.core/get map__10728 (quote contacts/phones)) ; =&gt; phone ; =&gt; (clojure.core/nth vec__10729 0 nil) ; =&gt; vec__10730 ; =&gt; (clojure.core/get map__10728 (quote contacts/emails)) ; =&gt; email ; =&gt; (clojure.core/nth vec__10730 0 nil) ; =&gt; first-name ; =&gt; (clojure.core/get map__10728 (quote first-name))]

We can make the result more readable and present it as code:

 1(let [data-map          personal-data
 2      data-map          (if (seq? data-map)
 3                          (apply hash-map (seq data-map))
 4                          data-map)
 5      phones-vector     (get data-map 'contacts/phones)
 6      emails-vector     (get data-map 'contacts/emails)
 7      first-name        (get data-map 'first-name)
 8      phone             (nth phones-vector 0 nil)
 9      email             (nth emails-vector 0 nil)]
10  {:first-name first-name
11   :phone      phone
12   :email      email})
13
14; => {:email "pw-at-gnu.org" :first-name "Paul" :phone 123456}
(let [data-map personal-data data-map (if (seq? data-map) (apply hash-map (seq data-map)) data-map) phones-vector (get data-map &#39;contacts/phones) emails-vector (get data-map &#39;contacts/emails) first-name (get data-map &#39;first-name) phone (nth phones-vector 0 nil) email (nth emails-vector 0 nil)] {:first-name first-name :phone phone :email email}) ; =&gt; {:email &#34;pw-at-gnu.org&#34; :first-name &#34;Paul&#34; :phone 123456}
Current section: Read Me Clojure
Categories:

Taxonomies: