Vars, Bindings, and Namespaces

One of the most important tools in a programming language is the ability to name and remember values or functions for later use. In Clojure, a namespace is a collection of names (symbols) that refer to vars. Each var is bound to a value. Let’s consider vars more closely.

Vars

When you define an object with def or defn, that object is stored in a Clojure var. For example, the following def creates a var named user/foo:

 (​def​ foo 10)
 -> #​'user/foo

The symbol user/foo refers to a var that is bound to the value 10. If you ask Clojure to evaluate the symbol foo, it will return the value of the associated var:

 foo
 -> 10

The initial value of a var is called its root binding. Sometimes it’s useful to have thread-local bindings for a var; this is covered in Managing Per-Thread State with Vars.

You can refer to a var directly. The var special form returns a var itself, not the var’s value:

 (var a-symbol)

You can use var to return the var bound to user/foo:

 (var foo)
 -> #​'user/foo

You will almost never see the var form directly in Clojure code. Instead, you’ll see the equivalent reader macro #’, which also returns the var for a symbol:

 #​'foo
 -> #​'user/foo

Why would you want to refer to a var directly? Most of the time, you won’t, and you can often simply ignore the distinction between symbols and vars.

But keep in the back of your mind that vars have many abilities other than just storing a value:

Bindings

Vars are bound to names, but there are other kinds of bindings as well. For example, in a function call, argument values bind to parameter names. In the following call, the name number is locally bound to the value 10 inside the triple function:

 (​defn​ triple [number] (* 3 number))
 -> #​'user/triple
 
 (triple 10)
 -> 30

A function’s parameter bindings have a lexical scope: they’re visible only inside the text of the function body. Functions are not the only way to create a lexical binding. The special form let does nothing other than create a set of lexical bindings:

 (​let​ [bindings*] exprs*)

The bindings are then in effect for exprs, and the value of the let is the value of the last expression in exprs.

Imagine that you want coordinates for the four corners of a square, given the bottom, left, and size. You can let the top and right coordinates, based on the values given:

 (​defn​ square-corners [bottom left size]
  (​let​ [top (+ bottom size)
  right (+ left size)]
  [[bottom left] [top left] [top right] [bottom right]]))

The let binds top and right. This saves you the trouble of calculating top and right more than once. (Both are needed twice to generate the return value.) The let then returns its last form, which in this example becomes the return value of square-corners.

Destructuring

In many programming languages, you bind a variable to an entire collection when you need to access only part of the collection.

Imagine that you’re working with a database of book authors. You track both first and last names, but some functions need to use only the first name:

 (​defn​ greet-author-1 [author]
  (println ​"Hello,"​ (:first-name author)))

The greet-author-1 function works fine:

 (greet-author-1 {:last-name ​"Vinge"​ :first-name ​"Vernor"​})
 | Hello, Vernor

Having to bind author is unsatisfying. You don’t need the author; all you need is the first-name. Clojure solves this with destructuring. Any place that you bind names, you can nest a vector or a map in the binding to reach into a collection and bind only the part you want. Here is a variant of greet-author that binds only the first name:

 (​defn​ greet-author-2 [{fname :first-name}]
  (println ​"Hello,"​ fname))

The binding form {fname :first-name} tells Clojure to bind fname to the :first-name of the function argument. greet-author-2 behaves just like greet-author-1:

 (greet-author-2 {:last-name ​"Vinge"​ :first-name ​"Vernor"​})
 | Hello, Vernor

Just as you can use a map to destructure any associative collection, you can use a vector to destructure any sequential collection. For example, you could bind only the first two coordinates in a three-dimensional coordinate space:

 (​let​ [[x y] [1 2 3]]
 [x y])
 -> [1 2]

The expression [x y] destructures the vector [1 2 3], binding x to 1 and y to 2. Since no symbol lines up with the final element 3, it’s not bound to anything.

Sometimes you want to skip elements at the start of a collection. Here’s how you could bind only the z coordinate:

 (​let​ [[_ _ z] [1 2 3]]
 z)
 -> 3

The underscore (_) is a legal symbol and is often used to indicate, “I don’t care about this binding.” Binding proceeds from left to right, so the _ is actually bound twice:

 ; *not* idiomatic!
 (​let​ [[_ _ z] [1 2 3]]
 _)
 -> 2

It’s also possible to simultaneously bind both a collection and elements within the collection. Inside a destructuring expression, an :as clause gives you a binding for the entire enclosing structure. For example, you could capture the x and y coordinates individually, plus the entire collection as coords, to report the total number of dimensions:

 (​let​ [[x y :as coords] [1 2 3 4 5 6]]
 (str ​"x: "​ x ​", y: "​ y ​", total dimensions "​ (count coords)))
 -> ​"x: 1, y: 2, total dimensions 6"

Try using destructuring to create an ellipsize function. ellipsize should take a string and return the first three words followed by an ellipsis (...).

 (require '[clojure.string :as str])
 (​defn​ ellipsize [words]
  (​let​ [[w1 w2 w3] (str/split words #​"s+"​)]
  (str/join ​" "​ [w1 w2 w3 ​"..."​])))
 (ellipsize ​"The quick brown fox jumps over the lazy dog."​)
 -> ​"The quick brown ..."

split splits the string around whitespace, and then the destructuring form [w1 w2 w3] grabs the first three words. The destructuring ignores any extra items, which is exactly what we want. Finally, join reassembles the three words, adding the ellipsis at the end.

Destructuring has several other features not shown here and is a mini-language in itself. The Snake game in A Clojure Snake makes heavy use of destructuring. For a complete list of destructuring options, see the online documentation for binding forms[14] and the destructuring guide.[15]

Namespaces

Root bindings live in a namespace. You can see evidence of this when you start the Clojure REPL and create a binding:

 user=>​ (​def​ foo 10)
 -> #​'user/foo

The user=> prompt tells you that you’re currently working in the user namespace. Most of the REPL session listings in this book omit the REPL prompt for brevity. In this section, the REPL prompt will be included whenever the current namespace is important. You should treat user as a scratch namespace for exploratory development.

When Clojure resolves the name foo, it namespace-qualifies foo in the current namespace user. You can verify this by calling resolve:

 (resolve sym)

resolve returns the var or class that a symbol will resolve to in the current namespace. Use resolve to explicitly resolve the symbol foo:

 (resolve ​'foo​)
 -> #​'user/foo

You can switch namespaces, creating a new one if needed, with in-ns:

 (in-ns name)

Try creating a myapp namespace:

 user=>​ (in-ns ​'myapp​)
 -> #object[clojure.lang.Namespace 0x5b025dc7 ​"myapp"​]
 myapp=>

Now you’re in the myapp namespace, and anything you def or defn will belong to myapp.

When you create a new namespace with in-ns, the java.lang package is automatically available to you:

 myapp=>​ String
 -> java.lang.String

While you’re learning Clojure, you should use the clojure.core namespace whenever you move to a new namespace, making Clojure’s core functions available in the new namespace as well:

 myapp=>​ (clojure.core/use ​'clojure.core​)
 -> nil

By default, the class names outside java.lang must be fully qualified. You can’t just say File:

 myapp=>​ File/separator
 -> java.lang.Exception​:​ No such namespace​:​ File

Instead, you must specify the fully qualified java.io.File. Note that your file separator character may be different from that shown here:

 myapp=>​ java.io.File/separator
 -> ​"/"

If you want to use a short name, rather than a fully qualified class name, you can import classes from a Java package into the current namespace using import.

 (import '(package Class+))

Once you import a class, you can use its short name:

 (import '(java.io InputStream File))
 -> java.io.File
 
 (.exists (File. ​"/tmp"​))
 -> true

import is only for Java classes. To use a Clojure var from another namespace without the namespace qualified, you must refer the external vars into the current namespace. For example, take Clojure’s split function that resides in clojure.string:

 (require ​'clojure.string​)
 (clojure.string/split ​"Something,separated,by,commas"​ #​","​)
 -> [​"Something"​ ​"separated"​ ​"by"​ ​"commas"​]
 
 (split ​"Something,separated,by,commas"​ #​","​)
 -> Unable to resolve symbol​:​ split in this context

If you wish to refer to split with a namespace alias, call require on split’s namespace and give it the alias str:

 (require '[clojure.string :as str])
 (str/split ​"Something,separated,by,commas"​ #​","​)
  -> [​"Something"​ ​"separated"​ ​"by"​ ​"commas"​]

This simple form of require causes the current namespace to reference all public vars in clojure.string via the alias str.

It’s common to import Java classes and require namespaces at the top of a source file, using the ns macro:

 (ns name & references)

The ns macro sets the current namespace (available as *ns*) to name, creating the namespace if necessary. The references can include :import, :require, and :use, which work like the similarly named functions to set up the namespace mappings in a single form at the top of a source file. For example, the following call to ns appears at the top of the sample code for this chapter:

 (ns examples.exploring
  (:require [clojure.string :as str])
  (:import (java.io File)))

The namespace functions can do quite a bit more than we’ve shown here.

You can examine namespaces and add or remove mappings at any time. To find out more, issue this command at the REPL. Since we’ve moved around a bit in the REPL, we’ll also ensure that we’re in the user namespace so that our REPL utilities are available to us:

 (in-ns ​'user​)
 (find-doc ​"ns-"​)

Alternately, browse the reference documentation.[16]

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

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