Clojure from the ground up

Names, numbers and text

Naming a value with def, the kind of numbers Clojure keeps without rounding, and the difference between a string and a keyword.

Part 2 of 12

Beginner15 min readBack to Courses

A name is a signpost

(def price 6) puts the value 6 behind the name price. The name is not a box that holds the number; it is a signpost pointing at it. Reading the name gives you the value, and every line below that one can use the name too. The signpost idea matters when the value is a collection: the name points at the whole vector or map, not at a copy, so two names can point at the same value without either being able to change it.

Names in Clojure are written with letters, digits, dashes and question marks. By convention a dash separates the words, and a question mark ends a name that answers yes or no, as in in-stock?.

Numbers that keep their shape

Clojure does not throw the fraction away when you divide whole numbers. (/ 10 2) is 5, and (/ 10 4) is written 5/2: the exact answer, kept as a fraction rather than rounded into a decimal. Ask for a decimal by writing a decimal point in one of the numbers, and reach for quot when you truly want the fraction dropped. A ratio is a pair of whole numbers, so arithmetic on ratios stays exact: (+ 1/2 1/3) answers 5/6 rather than a decimal that is close to it.

Exact arithmetic is why money and measurements behave in Clojure. Ten cents added a hundred times is one exact amount, not a number that is nearly right.

Text, and labels that name themselves

A string is text in double quotes: "Ada". A keyword is written with a colon on the front, like :clojure, and it is not text at all — it is a label that knows its own name. Two keywords with the same spelling are the same value, so they compare instantly and make reliable map keys. Keywords are the usual way to name the fields of a record, which the next chapter is about.

(str a b) joins values into one string, and it takes numbers, keywords and strings in the same call. (name :clojure) hands you the text without the colon, and (keyword "clojure") goes the other way.

A name is looked up when it is used

A name in Clojure is not a piece of text replaced before the program runs. When a function body says rate, the value is looked up at the moment the function is called. Redefine rate later and the same function uses the new value the next time it runs. The body is compiled once, but the lookup happens per call, which is why a later definition can change the behaviour of code that was written before it.

That is why a top-level name is a place to point at a value, not a constant baked into the code. The name must exist before the line that refers to it is compiled, but the value it points at can be changed later for every caller.

Where exactness ends

A ratio stays exact only while every number in the calculation is whole. Mix in one decimal and the whole sum becomes a machine number: (/ 1 3) prints as 1/3, while (/ 1.0 3) prints as a long decimal that cannot represent a third exactly. The ratio is two whole numbers; the float is a binary fraction with a fixed number of bits, and a third has no finite binary expansion.

That is not a bug in division; it is a change of kind. Choose exact numbers for money and counts, and decimals for measurements that were approximate to begin with. When you print a decimal, decide the number of places yourself instead of showing whatever the machine produced.

A name, three numbers and two kinds of text

; def puts a value behind a name.
(def price 6)
(def qty 7)
(def label "Total")

(println (* price qty))
(println (/ 10 2))
(println (/ 10 4))
(println (quot 10 4))

; str joins values of any kind into one string.
(println (str label ": " (* price qty)))
(println (str 2 " apples"))

; A keyword is a label that knows its name.
(println :clojure)
(println (name :clojure))
(println (keyword "clojure"))

A price, a total and a label

Run it and read the seven lines. Then add a `def` for a tax rate, print the total with the tax added, and use `str` to print the same sentence with the price first.

Output
Click "Run" to execute your code

Reach for a keyword when the value names a choice or a field, and a string when it is text somebody will read. The two agree when you need them to, because (= :ada (keyword "ada")) is true, but a keyword prints as itself and compares instantly.

Three habits from other languages cause trouble. = compares values, not names, so (= :ada "ada") is false: a keyword and a string are different kinds even when they read alike. (str :ada) includes the colon, because str prints the value the way prn would; use (name :ada) when you want the bare word. And (/ 10 4) is not 2: whole-number division keeps the fraction unless you ask for quot.

Try these next

  • Make two names, one for a price and one for a quantity, and print a sentence built with str.
  • Work out (/ 7 2), (quot 7 2) and (rem 7 2), and say what each answer means.
  • Turn :total into text with name, then back into a keyword, and print both.

Splitting a bill exactly

Run it and read the eight lines. Then change the total to 101 and the people to 4, and add a line that prints the leftover as a sentence.

Output
Click "Run" to execute your code

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

What does (def price 6) do?