(ns-name *ns*)
; => user
Organizing Your Project: A Librarian’s Tale
Within each of us lives a librarian named Melvil, a fantastical creature who delights in the organizational arts. Day and night, Melvil yearns to bring order to your codebase. Fortunately, Clojure provides a suite of tools designed specifically to aid this homunculus in its constant struggle against the forces of chaos.
These tools help you organize your code by grouping together related functions and data. They also prevent name collisions so you don’t accidentally overwrite someone else’s code or vice versa. Join me in a tale of suspense and mystery as you learn how to use these tools and solve the heist of a lifetime! By the end of the saga, you’ll understand the following:
- What
def
does - What namespaces are and how to use them
- The relationship between namespaces and the filesystem
- How to use
refer
,alias
,require
,use
, andns
- How to organize Clojure projects using the filesystem
I’ll start with a high-level overview of Clojure’s organizational system, which works much like a library. Melvil quivers with excitement!
Your Project as a Library
Real-world libraries store collections of objects, such as books, magazines, and DVDs. They use addressing systems, so when you’re given an object’s address, you can navigate to the physical space and retrieve the object.
Of course, no human being would be expected to know offhand what a book’s or DVD’s address is. That’s why libraries record the association between an object’s title and its address and provide tools for searching these records. In ye olden times before computers, libraries provided card catalogs, which were cabinets filled with paper cards containing each book’s title, author, “address” (its Dewey decimal or Library of Congress number), and other info.
For example, to find The Da Vinci Code, you would riffle through the title catalog (cards ordered by title) until you found the correct card. On that card you would see the address 813.54 (if it’s using the Dewey decimal system), navigate your library to find the shelf where The Da Vinci Code resides, and engage in the literary and/or hate-reading adventure of your lifetime.
It’s useful to imagine a similar setup in Clojure. I think of Clojure as storing objects (like data structures and functions) in a vast set of numbered shelves. No human being could know offhand which shelf an object is stored in. Instead, we give Clojure an identifier that it uses to retrieve the object.
For this to be successful, Clojure must maintain the associations between our identifiers and shelf addresses. It does this by using namespaces. Namespaces contain maps between human-friendly symbols and references to shelf addresses, known as vars, much like a card catalog.
Technically, namespaces are objects of type clojure.lang.Namespace
, and you can interact with them just like you can with Clojure data structures. For example, you can refer to the current namespace with *ns*
, and you can get its name with (ns-name *ns*)
:
When you start the REPL, for example, you’re in the user
namespace (as you can see here). The prompt shows the current namespace using something like user=>
.
The idea of a current namespace implies that you can have more than one, and indeed Clojure allows you to create as many namespaces as you want (although technically, there might be an upper limit to the number of names you can create). In Clojure programs, you are always in a namespace.
As for symbols, you’ve been using them this entire time without even realizing it. For example, when you write (map inc [1 2])
, both map
and inc
are symbols. Symbols are data types within Clojure, and I’ll explain them thoroughly in the next chapter. For now, all you need to know is that when you give Clojure a symbol like map
, it finds the corresponding var in the current namespace, gets a shelf address, and retrieves an object from that shelf for you—in this case, the function that map
refers to. If you want to just use the symbol itself, and not the thing it refers to, you have to quote it. Quoting any Clojure form tells Clojure not to evaluate it but to treat it as data. The next few examples show what happens when you quote a form.
➊ inc
; => #<core$inc clojure.core$inc@30132014>
➋ 'inc
; => inc
➌ (map inc [1 2])
; => (2 3)
➍ '(map inc [1 2])
; => (map inc [1 2])
When you evaluate inc
in the REPL at ➊, it prints out the textual representation of the function that inc
refers to. Next, you quote inc
at ➋, so the result is the symbol inc
. Then, you evaluate a familiar map
application at ➌ and get a familiar result. After that, you quote the entire list data structure at ➍, resulting in an unevaluated list that includes the map
symbol, the inc
symbol, and a vector.
Now that you know about Clojure’s organization system, let’s look at how to use it.
Storing Objects with def
The primary tool in Clojure for storing objects is def
. Other tools like defn
use def
under the hood. Here’s an example of def in action:
(def great-books ["East of Eden" "The Glass Bead Game"])
; => #'user/great-books
great-books
; => ["East of Eden" "The Glass Bead Game"]
This code tells Clojure:
- Update the current namespace’s map with the association between
great-books
and the var. - Find a free storage shelf.
- Store
["East of Eden" "The Glass Bead Game"]
on the shelf. - Write the address of the shelf on the var.
- Return the var (in this case,
#'user/great-books
).
This process is called interning a var. You can interact with a namespace’s map of symbols-to-interned-vars using ns-interns
. Here’s how you’d get a map of interned vars:
(ns-interns *ns*)
; => {great-books #'user/great-books}
You can use the get
function to get a specific var:
(get (ns-interns *ns*) 'great-books)
; => #'user/great-books
By evaluating (
ns-map *ns*)
,
you can also get the full map that the namespace uses for looking up a var when given a symbol. (ns-map *ns*)
gives you a very large map that I won’t print here, but try it out!
#'user/great-books
is the reader form of a var. I’ll explain more about reader forms in Chapter 7. For now, just know that you can use #'
to grab hold of the var corresponding to the symbol that follows; #'user/great-books
lets you use the var associated with the symbol great-books
within the user
namespace. We can deref
vars to get the objects they point to:
(deref #'user/great-books)
; => ["East of Eden" "The Glass Bead Game"]
This is like telling Clojure, “Get the shelf number from the var, go to that shelf number, grab what’s on it, and give it to me!”
But normally, you would just use the symbol:
great-books
; => ["East of Eden" "The Glass Bead Game"]
This is like telling Clojure, “Retrieve the var associated with great-books and deref that bad Jackson.”
So far so good, right? Well, brace yourself, because this idyllic paradise of organization is about to be turned upside down! Call def
again with the same symbol:
(def great-books ["The Power of Bees" "Journey to Upstairs"])
great-books
; => ["The Power of Bees" "Journey to Upstairs"]
The var has been updated with the address of the new vector. It’s like you used white-out on the address on a card in the card catalog and then wrote a new address. The result is that you can no longer ask Clojure to find the first vector. This is referred to as a name collision. Chaos! Anarchy!
You may have experienced this in other programming languages. JavaScript is notorious for it, and it happens in Ruby as well. It’s a problem because you can unintentionally overwrite your own code, and you also have no guarantee that a third-party library won’t overwrite your code. Melvil recoils in horror! Fortunately, Clojure allows you to create as many namespaces as you like so you can avoid these collisions.
Creating and Switching to Namespaces
Clojure has three tools for creating namespaces: the function create-ns
, the function in-ns
, and the macro ns
. You’ll mostly use the ns
macro in your Clojure files, but I’ll hold off on explaining it for a few pages because it combines many tools, and it’s easier to understand after I discuss each of the other tools.
create-ns
takes a symbol, creates a namespace with that name if it doesn’t exist already, and returns the namespace:
user=> (create-ns 'cheese.taxonomy)
; => #<Namespace cheese.taxonomy>
You can use the returned namespace as an argument in a function call:
user=> (ns-name (create-ns 'cheese.taxonomy))
; => cheese-taxonomy
In practice, you’ll probably never use create-ns
in your code, because it’s not very useful to create a namespace and not move into it. Using in-ns
is more common because it creates the namespace if it doesn’t exist and switches to it, as shown in Listing 6-1.
user=> (in-ns 'cheese.analysis)
; => #<Namespace cheese.analysis>
- 6-1. Using in-ns to create a namespace and switch to it
Notice that your REPL prompt is now cheese.analysis>
, indicating that you are indeed in the new namespace you just created. Now when you use def
, it will store the named object in the cheese.analysis
namespace.
But what if you want to use functions and data from other namespaces? To do that, you can use a fully qualified symbol. The general form is namespace/
name:
cheese.analysis=> (in-ns 'cheese.taxonomy)
cheese.taxonomy=> (def cheddars ["mild" "medium" "strong" "sharp" "extra sharp"])
cheese.taxonomy=> (in-ns 'cheese.analysis)
cheese.analysis=> cheddars
; => Exception: Unable to resolve symbol: cheddars in this context
This creates a new namespace, cheese.taxonomy
, defines cheddars
in that namespace, and then switches back to the cheese.analysis
namespace. You’ll get an exception if you try to refer to the cheese.taxonomy
namespace’s cheddars
from within cheese.analysis
, but using the fully qualified symbol works:
cheese.analysis=> cheese.taxonomy/cheddars
; => ["mild" "medium" "strong" "sharp" "extra sharp"]
Typing these fully qualified symbols can quickly become a nuisance. For instance, say I’m an extremely impatient academic specializing in semiotics-au-fromage, or the study of symbols as they relate to cheese.
Suddenly, the worst conceivable thing that could possibly happen happens! All across the world, sacred and historically important cheeses have gone missing. Wisconsin’s Standard Cheddar: gone! The Great Cheese Jars of Tutankhamun: stolen! The Cheese of Turin: replaced with a hoax cheese! This threatens to throw the world into total chaos for some reason! Naturally, as a distinguished cheese researcher, I am honor-bound to solve this mystery. Meanwhile, I’m being chased by the Illuminati, the Freemasons, and the Foot Clan!
Because I’m an academic, I attempt to solve this mystery the best way I know how—by heading to the library and researching the shit out of it. My trusty assistant, Clojure, accompanies me. As we bustle from namespace to namespace, I shout at Clojure to hand me one thing after another.
But Clojure is kind of dumb and has a hard time figuring out what I’m referring to. From within the user
namespace, I belt out, “join
! Give me join
!”—specks of spittle flying from my mouth. “RuntimeException: Unable to resolve symbol: join
,” Clojure whines in response. “For the love of brie, just hand me clojure.string/join
!” I retort, and Clojure dutifully hands me the function I was looking for.
My voice gets hoarse. I need some way to tell Clojure what objects to get me without having to use the fully qualified symbol every. damn. time.
Luckily, Clojure provides the refer
and alias
tools that let me yell at it more succinctly.
refer
refer
gives you fine-grained control over how you refer to objects in other namespaces. Fire up a new REPL session and try the following. Keep in mind that it’s okay to play around with namespaces like this in the REPL, but you don’t want your Clojure files to look like this; the proper way to structure your files is covered in “Real Project Organization” on page 133.
user=> (in-ns 'cheese.taxonomy)
cheese.taxonomy=> (def cheddars ["mild" "medium" "strong" "sharp" "extra sharp"])
cheese.taxonomy=> (def bries ["Wisconsin" "Somerset" "Brie de Meaux" "Brie de Melun"])
cheese.taxonomy=> (in-ns 'cheese.analysis)
cheese.analysis=> (clojure.core/refer 'cheese.taxonomy)
cheese.analysis=> bries
; => ["Wisconsin" "Somerset" "Brie de Meaux" "Brie de Melun"]
cheese.analysis=> cheddars
; => ["mild" "medium" "strong" "sharp" "extra sharp"]
This code creates a cheese.taxonomy
namespace and two vectors within it: cheddars
and bries
. Then it creates and moves to a new namespace called cheese.analysis
. Calling refer
with a namespace symbol lets you refer to the corresponding namespace’s objects without having to use fully qualified symbols. It does this by updating the current namespace’s symbol/object map. You can see the new entries like this:
cheese.analysis=> (clojure.core/get (clojure.core/ns-map clojure.core/*ns*) 'bries)
; => #'cheese.taxonomy/bries
cheese.analysis=> (clojure.core/get (clojure.core/ns-map clojure.core/*ns*) 'cheddars)
; => #'cheese.taxonomy/cheddars
It’s as if Clojure
- Calls
ns-interns
on thecheese.taxonomy
namespace - Merges that with the
ns-map
of the current namespace - Makes the result the new
ns-map
of the current namespace
When you call refer
, you can also pass it the filters :only
, :exclude
, and :rename
. As the names imply, :only
and :exclude
restrict which symbol/var mappings get merged into the current namespace’s ns-map
. :rename
lets you use different symbols for the vars being merged in. Here’s what would happen if we had modified the preceding example to use :only
:
cheese.analysis=> (clojure.core/refer 'cheese.taxonomy :only ['bries])
cheese.analysis=> bries
; => ["Wisconsin" "Somerset" "Brie de Meaux" "Brie de Melun"]
cheese.analysis=> cheddars
; => RuntimeException: Unable to resolve symbol: cheddars
And here’s :exclude
in action:
cheese.analysis=> (clojure.core/refer 'cheese.taxonomy :exclude ['bries])
cheese.analysis=> bries
; => RuntimeException: Unable to resolve symbol: bries
cheese.analysis=> cheddars
; => ["mild" "medium" "strong" "sharp" "extra sharp"]
Lastly, a :rename
example:
cheese.analysis=> (clojure.core/refer 'cheese.taxonomy :rename {'bries 'yummy-bries})
cheese.analysis=> bries
; => RuntimeException: Unable to resolve symbol: bries
cheese.analysis=> yummy-bries
; => ["Wisconsin" "Somerset" "Brie de Meaux" "Brie de Melun"]
Notice that in these last examples we have to use the fully qualified names of all the objects in clojure.core
, like clojure.core/ns-map
and clojure.core/refer
. We didn’t have to do that in the user
namespace. That’s because the REPL automatically refers clojure.core
within the user
namespace. You can make your life easier by evaluating (clojure.core/refer-clojure)
when you create a new namespace; this will refer the clojure.core namespace, and I’ll be using it from now on. Instead of seeing clojure.core/refer
in the examples, you’ll only see refer
.
Another thing to notice is that you have complete freedom over how you organize your functions and data across namespaces. This lets you sensibly group related functions and data together in the same namespace.
Sometimes you may want a function to be available only to other functions within the same namespace. Clojure allows you to define private functions using defn-
:
(in-ns 'cheese.analysis)
;; Notice the dash after "defn"
(defn- private-function
"Just an example function that does nothing"
[])
If you try to call this function from another namespace or refer it, Clojure will throw an exception. You can see this when you evaluate the code at ➊ and ➋:
cheese.analysis=> (in-ns 'cheese.taxonomy)
cheese.taxonomy=> (clojure.core/refer-clojure)
➊ cheese.taxonomy=> (cheese.analysis/private-function)
➋ cheese.taxonomy=> (refer 'cheese.analysis :only ['private-function])
As you can see, even if you explicitly refer
the function, you can’t use the function from another namespace, because you made it private. (If you want to be tricky, you can still access the private var using the arcane syntax @#'some/private-var
, but you’ll rarely want to do that.)
alias
Compared to refer
, alias
is relatively simple. All it does is let you shorten a namespace name for using fully qualified symbols:
cheese.analysis=> (clojure.core/alias 'taxonomy 'cheese.taxonomy)
cheese.analysis=> taxonomy/bries
; => ["Wisconsin" "Somerset" "Brie de Meaux" "Brie de Melun"]
This code lets us use call symbols from the cheese.taxonomy
namespace with the shorter alias taxonomy
.
refer
and alias
are your two basic tools for referring to objects outside your current namespace! They’re great aids to REPL development.
However, it’s unlikely that you’d create your entire program in the REPL. In the next section, I’ll cover everything you need to know to organize a real project with source code living on the filesystem.
Real Project Organization
Now that I’ve covered the building blocks of Clojure’s organization system, I’ll show you how to use them in real projects. I’ll discuss the relationship between file paths and namespace names, explain how to load a file with require
and use
, and show how to use ns
to set up a namespace.
The Relationship Between File Paths and Namespace Names
To kill two birds with one stone (or feed two birds with one seed, depending on how much of a hippie you are), I’ll cover more on namespaces while we work on catching the pesky international cheese thief by mapping the locations of his heists. Run the following:
lein new app the-divine-cheese-code
This should create a directory structure that looks like this:
| .gitignore
| doc
| | intro.md
| project.clj
| README.md
| resources
| src
| | the_divine_cheese_code
| | | core.clj
| test
| | the_divine_cheese_code
| | | core_test.clj
Now, open src/the_divine_cheese_code/core.clj. You should see this on the first line:
(ns the-divine-cheese-code.core
(:gen-class))
ns
is the primary way to create and manage namespaces within Clojure. I’ll explain it in full shortly. For now, though, just know that this line is very similar to the in-ns
function we used in Listing 6-1. It creates a namespace if it doesn’t exist and then switches to it. I also cover (:gen-class)
in more detail in Chapter 12.
The name of the namespace is the-divine-cheese-code.core
. In Clojure, there’s a one-to-one mapping between a namespace name and the path of the file where the namespace is declared, according to the following conventions:
- When you create a directory with
lein
(as you did here), the source code’s root is src by default. - Dashes in namespace names correspond to underscores in the filesystem. So
the-divine-cheese-code
is mapped to the_divine_cheese_code on the filesystem. - The component preceding a period (
.
) in a namespace name corresponds to a directory. For example, sincethe-divine-cheese-code.core
is the namespace name, the_divine_cheese_code is a directory. - The final component of a namespace corresponds to a file with the .clj extension;
core
is mapped to core.clj.
Your project will have one more namespace, the-divine-cheese-code.visualization.svg
. Go ahead and create the file for it now:
mkdir src/the_divine_cheese_code/visualization
touch src/the_divine_cheese_code/visualization/svg.clj
Notice that the filesystem path follows these conventions. With the relationship between namespaces and the filesystem down, let’s look at require
and use
.
Requiring and Using Namespaces
The code in the the-divine-cheese-code.core
namespace will use the functions in the namespace the-divine-cheese-code.visualization.svg
to create SVG markup. To use svg
’s functions, core
will have to require it. But first, let’s add some code to svg.clj. Make it look like this (you’ll add more later):
(ns the-divine-cheese-code.visualization.svg)
(defn latlng->point
"Convert lat/lng map to comma-separated string"
[latlng]
(str (:lat latlng) "," (:lng latlng)))
(defn points
[locations]
(clojure.string/join " " (map latlng->point locations)))
This defines two functions, latlng->point
and points
, which you’ll use to convert a seq of latitude/longitude coordinates into a string of points. To use this code from the core.clj file, you have to require
it. require
takes a symbol designating a namespace and ensures that the namespace exists and is ready to be used; in this case, when you call (require 'the-divine-cheese
-code.visualization.svg)
, Clojure reads and evaluates the corresponding file. By evaluating the file, it creates the the-divine-cheese-code.visualization.svg
namespace and defines the functions latlng->point
and points
within that namespace. Even though the file svg.clj is in your project’s directory, Clojure doesn’t automatically evaluate it when it runs your project; you have to explicitly tell Clojure that you want to use it.
After requiring the namespace, you can refer it so that you don’t have to use fully qualified names to reference the functions. Go ahead and require the-divine-cheese-code.visualization.svg
and add the heists
seq to make core.clj match the listing:
(ns the-divine-cheese-code.core)
;; Ensure that the SVG code is evaluated
(require 'the-divine-cheese-code.visualization.svg)
;; Refer the namespace so that you don't have to use the
;; fully qualified name to reference svg functions
(refer 'the-divine-cheese-code.visualization.svg)
(def heists [{:location "Cologne, Germany"
:cheese-name "Archbishop Hildebold's Cheese Pretzel"
:lat 50.95
:lng 6.97}
{:location "Zurich, Switzerland"
:cheese-name "The Standard Emmental"
:lat 47.37
:lng 8.55}
{:location "Marseille, France"
:cheese-name "Le Fromage de Cosquer"
:lat 43.30
:lng 5.37}
{:location "Zurich, Switzerland"
:cheese-name "The Lesser Emmental"
:lat 47.37
:lng 8.55}
{:location "Vatican City"
:cheese-name "The Cheese of Turin"
:lat 41.90
:lng 12.45}])
(defn -main
[& args]
(println (points heists)))
Now you have a seq of heist locations to work with and you can use functions from the visualization.svg
namespace. The main
function simply applies the points
function to heists
. If you run the project with lein run
, you should see this:
50.95,6.97 47.37,8.55 43.3,5.37 47.37,8.55 41.9,12.45
Hooray! You’re one step closer to catching that purloiner of the fermented curd! Using require
successfully loaded the-divine-cheese-code.visualization.svg
for use.
The details of require
are actually a bit complicated, but for practical purposes you can think of require
as telling Clojure the following:
- Do nothing if you’ve already called
require
with this symbol (the-divine-cheese-code.visualization.svg
). - Otherwise, find the file that corresponds to this symbol using the rules described in “The Relationship Between File Paths and Namespace Names” on page 133. In this case, Clojure finds
src/the_divine_cheese_code/visualization/svg.clj
.
Read and evaluate the contents of that file. Clojure expects the file to declare a namespace corresponding to its path (which ours does).
require
also lets you alias a namespace when you require it, using :as
or alias
. This:
(require '[the-divine-cheese-code.visualization.svg :as svg])
is equivalent to this:
(require 'the-divine-cheese-code.visualization.svg)
(alias 'svg 'the-divine-cheese-code.visualization.svg)
You can now use the aliased namespace:
(svg/points heists)
; => "50.95,6.97 47.37,8.55 43.3,5.37 47.37,8.55 41.9,12.45"
Clojure provides another shortcut. Instead of calling require
and refer
separately, the function use
does both. It’s frowned upon to use use
in production code, but it’s handy when you’re experimenting in the REPL and you want to quickly get your hands on some functions. For example, this:
(require 'the-divine-cheese-code.visualization.svg)
(refer 'the-divine-cheese-code.visualization.svg)
is equivalent to this:
(use 'the-divine-cheese-code.visualization.svg)
You can alias a namespace with use
just like you can with require
. This:
(require 'the-divine-cheese-code.visualization.svg)
(refer 'the-divine-cheese-code.visualization.svg)
(alias 'svg 'the-divine-cheese-code.visualization.svg)
is equivalent to the code in Listing 6-2, which also shows aliased namespaces being used in function calls.
(use '[the-divine-cheese-code.visualization.svg :as svg])
(= svg/points points)
; => true
(= svg/latlng->point latlng->point)
; => true
- 6-2. Sometimes it’s handy to both use and alias a namespace.
It may seem redundant to alias a namespace with use
here because use
already refers the namespace (which lets you simply call points
instead of svg/points
). In certain situations, though, it’s handy because use
takes the same options as refer
(:only
, :exclude
, :as
, and :rename
). You might want to alias a namespace with use
when you’ve skipped referring a symbol. You could use this:
(require 'the-divine-cheese-code.visualization.svg)
(refer 'the-divine-cheese-code.visualization.svg :as :only ['points])
Or you could use the use
form in Listing 6-3 (which also includes examples of how you can call functions).
(use '[the-divine-cheese-code.visualization.svg :as svg :only [points]])
(refer 'the-divine-cheese-code.visualization.svg :as :only ['points])
(= svg/points points)
; => true
;; We can use the alias to reach latlng->point
svg/latlng->point
; This doesn't throw an exception
;; But we can't use the bare name
latlng->point
; This does throw an exception!
- Aliasing a namespace after you use it lets you refer to symbols that you excluded.
If you try Listing 6-3 in a REPL and latlng->point
doesn’t throw an exception, it’s because you referred latlng->point
in Listing 6-2. You’ll need to restart your REPL session for the code to behave as shown in Listing 6-3.
The takeaway here is that require
and use
load files and optionally alias
or refer
their namespaces. As you write Clojure programs and read code written by others, you might encounter even more ways of writing require
and use
, at which point it’ll make sense to read Clojure’s API docs (http://clojure.org/libs/) to understand what’s going on. However, what you’ve learned so far about require
and use
should cover 95.3 percent of your needs.
The ns Macro
Now it’s time to look at the ns
macro. The tools covered so far—in-ns
, refer
, alias
, require
, and use
—are most often used when you’re playing in the REPL. In your source code files, you’ll typically use the ns
macro because it allows you to use the tools described so far succinctly and provides other useful functionality. In this section, you’ll learn about how one ns
call can incorporate require
, use
, in-ns
, alias
, and refer
.
One useful task ns
does is refer the clojure.core
namespace by default. That’s why you can call println
from within the-divine-cheese-code.core
without using the fully qualified name, clojure.core/println
.
You can control what gets referred from clojure-core
with :refer-clojure
, which takes the same options as refer
:
(ns the-divine-cheese-code.core
(:refer-clojure :exclude [println]))
If you called this at the beginning of divine_cheese_code.core.clj, it would break your code, forcing you to use clojure.core/println
within the -main
function.
Within ns
, the form (:
refer-clojure)
is called a reference. This might look weird to you. Is this reference a function call? A macro? What is it? You’ll learn more about the underlying machinery in Chapter 7. For now, you just need to understand how each reference maps to function calls. For example, the preceding code is equivalent to this:
(in-ns 'the-divine-cheese-code.core)
(refer 'clojure.core :exclude ['println])
There are six possible kinds of references within ns
:
(:refer-clojure)
(:require)
(:use)
(:import)
(:load)
(:gen-class)
(:import)
and (:gen-class)
are covered in Chapter 12. I won’t cover (:load)
because it is seldom used.
(:require)
works a lot like the require
function. For example, this:
(ns the-divine-cheese-code.core
(:require the-divine-cheese-code.visualization.svg))
is equivalent to this:
(in-ns 'the-divine-cheese-code.core)
(require 'the-divine-cheese-code.visualization.svg)
Notice that in the ns
form (unlike the in-ns
function call), you don’t have to quote your symbol with '
. You never have to quote symbols within ns
.
You can also alias
a library that you require
within ns
, just like when you call the function. This:
(ns the-divine-cheese-code.core
(:require [the-divine-cheese-code.visualization.svg :as svg]))
is equivalent to this:
(in-ns 'the-divine-cheese-code.core)
(require ['the-divine-cheese-code.visualization.svg :as 'svg])
You can require multiple libraries in a (:require)
reference as follows. This:
(ns the-divine-cheese-code.core
(:require [the-divine-cheese-code.visualization.svg :as svg]
[clojure.java.browse :as browse]))
is equivalent to this:
(in-ns 'the-divine-cheese-code.core)
(require ['the-divine-cheese-code.visualization.svg :as 'svg])
(require ['clojure.java.browse :as 'browse])
However, one difference between the (:require)
reference and the require
function is that the reference also allows you to refer names. This:
(ns the-divine-cheese-code.core
(:require [the-divine-cheese-code.visualization.svg :refer [points]]))
is equivalent to this:
(in-ns 'the-divine-cheese-code.core)
(require 'the-divine-cheese-code.visualization.svg)
(refer 'the-divine-cheese-code.visualization.svg :only ['points])
You can also refer all symbols (notice the :all
keyword):
(ns the-divine-cheese-code.core
(:require [the-divine-cheese-code.visualization.svg :refer :all]))
which is the same as doing this:
(in-ns 'the-divine-cheese-code.core)
(require 'the-divine-cheese-code.visualization.svg)
(refer 'the-divine-cheese-code.visualization.svg)
This is the preferred way to require code, alias namespaces, and refer symbols. It’s recommended that you not use (:use)
, but since it’s likely that you’ll come across it, it’s good to know how it works. You know the drill. This:
(ns the-divine-cheese-code.core
(:use clojure.java.browse))
does this:
(in-ns 'the-divine-cheese-code.core)
(use 'clojure.java.browse)
whereas this:
(ns the-divine-cheese-code.core
(:use [clojure.java browse io]))
does this:
(in-ns 'the-divine-cheese-code.core)
(use 'clojure.java.browse)
(use 'clojure.java.io)
Notice that when you follow :use
with a vector, it takes the first symbol as the base and then calls use
with each symbol that follows.
Oh my god, that’s it! Now you can use ns
like a pro! And you’re going to need to, dammit, because that voleur des fromages (as they probably say in French) is still running amok! Remember him/her?!
To Catch a Burglar
We can’t allow this plunderer of parmesan to make off with any more cheese! It’s time to finish drawing lines based on the coordinates of the heists! That will surely reveal something!
Using the latitude coordinates for each heist, you’ll connect the dots in an SVG image. But if you draw lines using the given coordinates, the result won’t look right for two reasons. First, latitude coordinates ascend from south to north, whereas SVG y-coordinates ascend from top to bottom. In other words, you need to flip the coordinates or the drawing will be upside down.
Second, the drawing will be very small. To fix that, you’ll zoom in on it by translating and scaling it. It’s like turning a drawing that looks like Figure 6-1a into Figure 6-1b.
Honestly, this is all completely arbitrary and it’s no longer directly related to code organization, but it’s fun and I think you’ll have a good time going through the code! Make your svg.clj file match Listing 6-4:
(ns the-divine-cheese-code.visualization.svg
(:require [clojure.string :as s])
(:refer-clojure :exclude [min max]))
➊ (defn comparator-over-maps
[comparison-fn ks]
(fn [maps]
➋ (zipmap ks
➌ (map (fn [k] (apply comparison-fn (map k maps)))
ks))))
➍ (def min (comparator-over-maps clojure.core/min [:lat :lng]))
(def max (comparator-over-maps clojure.core/max [:lat :lng]))
- 6-3. Constructing map comparison functions
You define the comparator-over-maps
function at ➊. This is probably the trickiest bit, so bear with me. comparator-over-maps
is a function that returns a function. The returned function compares the values for the keys provided by the ks
parameter using the supplied comparison function, comparison-fn
.
You use comparator-over-maps
to construct the min
and max
functions ➍, which you’ll use to find the top-left and bottom-right corners of our drawing. Here’s min
in action:
(min [{:a 1 :b 3} {:a 5 :b 0}])
; => {:a 1 :b 0}
When you call min
, it calls zipmap
, which takes two arguments, both seqs, and returns a new map. The elements of the first seq become the keys, and the elements of the second seq become the values:
(zipmap [:a :b] [1 2])
; => {:a 1 :b 2}
At , the first argument to zipmap
is ks
, so the elements of ks
will be the keys of the returned map. The second argument is the result of the map call at ➌. That map call actually performs the comparison.
Finally, at ➍ you use comparator-over-maps
to create the comparison functions. If you think of the drawing as being inscribed in a rectangle, min
is the corner of the rectangle closest to (0, 0) and max
is the corner farthest from it.
Here’s the next part of the code:
(defn translate-to-00
[locations]
(let [mincoords (min locations)]
(map #(merge-with - % mincoords) locations)))
(defn scale
[width height locations]
(let [maxcoords (max locations)
ratio {:lat (/ height (:lat maxcoords))
:lng (/ width (:lng maxcoords))}]
(map #(merge-with * % ratio) locations)))
translate-to-00
, defined at , works by finding the min
of our locations and subtracting that value from each location. It uses merge-with
, which works like this:
(merge-with - {:lat 50 :lng 10} {:lat 5 :lng 5})
; => {:lat 45 :lng 5}
Then we define the function scale
at , which multiplies each point by the ratio between the maximum latitude and longitude and the desired height and width.
Here’s the rest of the code for svg.clj:
(defn latlng->point
"Convert lat/lng map to comma-separated string"
[latlng]
(str (:lat latlng) "," (:lng latlng)))
(defn points
"Given a seq of lat/lng maps, return string of points joined by space"
[locations]
(s/join " " (map latlng->point locations)))
(defn line
[points]
(str "<polyline points=\"" points "\" />"))
(defn transform
"Just chains other functions"
[width height locations]
(->> locations
translate-to-00
(scale width height)))
(defn xml
"svg 'template', which also flips the coordinate system"
[width height locations]
(str "<svg height=\"" height "\" width=\"" width "\">"
;; These two <g> tags change the coordinate system so that
;; 0,0 is in the lower-left corner, instead of SVG's default
;; upper-left corner
"<g transform=\"translate(0," height ")\">"
"<g transform=\"rotate(-90)\">"
(-> (transform width height locations)
points
line)
"</g></g>"
"</svg>"))
The functions here are pretty straightforward. They just take {:lat x :lng y}
maps and transform them so that an SVG can be created. latlng->point
returns a string that can be used to define a point in SVG markup. points
converts a seq of lat
/lng
maps into a space-separated string of points. line
returns the SVG markup for a line that connects all given space-separated strings of points. transform
takes a seq of locations, translates them so they start at the point (0, 0), and scales them to the given width and height. Finally, xml
produces the markup for displaying the given locations using SVG.
With svg.clj all coded up, now make core.clj look like this:
(ns the-divine-cheese-code.core
(:require [clojure.java.browse :as browse]
[the-divine-cheese-code.visualization.svg :refer [xml]])
(:gen-class))
(def heists [{:location "Cologne, Germany"
:cheese-name "Archbishop Hildebold's Cheese Pretzel"
:lat 50.95
:lng 6.97}
{:location "Zurich, Switzerland"
:cheese-name "The Standard Emmental"
:lat 47.37
:lng 8.55}
{:location "Marseille, France"
:cheese-name "Le Fromage de Cosquer"
:lat 43.30
:lng 5.37}
{:location "Zurich, Switzerland"
:cheese-name "The Lesser Emmental"
:lat 47.37
:lng 8.55}
{:location "Vatican City"
:cheese-name "The Cheese of Turin"
:lat 41.90
:lng 12.45}])
(defn url
[filename]
(str "file:///"
(System/getProperty "user.dir")
"/"
filename))
(defn template
[contents]
(str "<style>polyline { fill:none; stroke:#5881d8; stroke-width:3}</style>"
contents))
(defn -main
[& args]
(let [filename "map.html"]
(->> heists
(xml 50 100)
template
(spit filename))
(browse/browse-url (url filename))))
Nothing too complicated is going on here. Within -main
you build up the drawing using the xml
and template
functions, write the drawing to a file with spit
, and then open it with browse/browse-url
. You should try that now! Run lein run
and you’ll see something that looks like Figure 6-2.
Wait a minute . . . that looks a lot like . . . that looks a lot like a lambda. Clojure’s logo is a lambda . . . oh my god! Clojure, it was you this whole time!
Summary
You learned a lot in this chapter. At this point, you should have all the tools you need to start organizing your projects. You now know that namespaces organize maps between symbols and vars, and that vars are references to Clojure objects (data structures, functions, and so on). def
stores an object and updates the current namespace with a map between a symbol and a var that points to the object. You can create private functions with defn-
.
Clojure lets you create namespaces with create-ns
, but often it’s more useful to use in-ns
, which switches to the namespace as well. You’ll probably only use these functions in the REPL. When you’re in the REPL, you’re always in the current namespace. When you’re defining namespaces in a file rather than the REPL, you should use the ns
macro, and there’s a one-to-one relationship between a namespace and its path on the filesystem.
You can refer to objects in other namespaces by using the fully qualified name, like cheese.taxonomy/cheddars
. refer
lets you use names from other namespaces without having to fully qualify them, and alias
lets you use a shorter name for a namespace when you’re writing out a fully qualified name.
require
and use
ensure that a namespace exists and is ready to be used, and optionally let you refer
and alias
the corresponding namespaces. You should use ns
to call require
and use
in your source files. https://gist.github.com/ghoseb/287710/ is a great reference for all the vagaries of using ns
.
Lastly and most importantly, it ain’t easy being cheesy.