Reading Clojure, part 2

Welcome back

Have you gone over the fundamentals on Reading Clojure? Did you shake off that preconception that there’s some magic syntax to declaring and evaluating things beyond the list?

Great! Let’s now go over other things you might encounter when looking at a random source file. I’m first going to give you an overview of types and related things. After it, we’ll then we get into the good stuff like going over a project, and the weird stuff like ->, ->>, #, the quote and other squiggles.

Basics

Remember vectors?

1
2
3
[1 2 "tres"] ; This is a valid vector with three elements
[1, 2, "three", "cuatro"] ; This is also a valid vector, with four elements
[1,,, 2,] ; This is a valid vector with two elements

How come? That’s because in Clojure, commas are white space. You may use them if you feel they increase readability, but you are not obligated to.

Semi-colons do have a meaning, as you’ve probably surmised: they comment everything out until the end of the line.

1
2
3
4
1   ; This is an integer
1.5 ; This is a floating point number
"This is a string" ; and it uses double-quotes
2/3 ; This is not a division - this is a fraction

Keywords

Keywords have a colon in front of them, say, :id. You can think of them as an element that will get assigned a constant value behind the scenes.

Keywords are normally used as the key on hash maps (more on those soon) but they don’t need to be. They are their own type. You can have keywords inside lists or vectors.

1
[:id 1 2 :something 4]

You can of course also use them as parameters to functions.

1
(execute :reset)

You can also see ::id. This means that it’s a namespaced keyword. A non-namespaced keyword will have a global value that’s the same no matter where it’s referenced, a namespaced keyword is tied to the namespace it’s declared on. Think of them as the difference between a global constant, and one that’s static to a class.

Update: on /r/clojure, Alex D. Miller qualifies this as:

Note that :: does NOT mean that its a namespaced keyword (well, they are, but so are other things like :a/b). :: means that it’s an auto-resolved keyword and will use either the current namespace or the aliases of the current namespace to resolve the namespace qualifier.

That is accurate and the above is a slight oversimplification. I just didn’t want to get into those details ahead of time.

More on that when we get to namespaces.

Collections

You’ve seen these before:

This is a list: (1 2 3)

This is a vector: [1 2 3]

This is a vector containing vectors and numbers: [[1 2 3] [4 5] 6 7]

And here’s a new one:

This is a set:

1
#{1 2 3}

How are they different? Lists, as you’d expect, are easier to append to at the start, whereas vectors (arrays) are easier to append to at the end. Both store items on the order that you added them. Sets, on the other hand, don’t have an order and contain only unique elements.

This is a hashmap:

1
{:a 1 :b 2 :c 3}

Hashmaps expect to have an even number of elements. If you don’t add an even number of elements, the compiler will barf.

It’s customary to use keywords as the key, just because they’re convenient and lightweight, but you don’t have to. The following example is intentionally dirty to demonstrate this.

1
2
3
4
5
6
7
8
9
{1     "One" 
5 "Five"
:c "OMG a keyword"
:d 4/3
"OK" "As you wish"
[5 9] "Just throw in whatever"
+ "Oh, we're indexing by functions now?"
inc 2 ; Yep, definitely indexing by identifiers
}

We get stuff out of a hash map with get.

1
2
3
(get {:a 1 :b 2 :c 19 } :c) ; returns 19
(get {:a 1 [5 9] 3/4} [5 9]) ; returns 3/4
(get {inc 1 :b 2 :c 19 } inc) ; returns 1

Why can we just index a map by a function like inc? Why doesn’t the compiler just throw a hissy fit?

Remember that inc is an identifier that happens to point to a function. It’s a value like any other. When Clojure encounters it as the first element of a list, it’ll invoke the function that it points to. But you can also use its value on any other context, like that deranged map above.

Alternatively, keywords can also double as a getter for a hashmap (which is another reason to use them).

1
(:a {:a 1 :b 3 :c 9})

Declaring things

If you see def, that means someone’s binding a value to an identifier.

1
2
3
4
(def the-value 2)
(def the-list [1 2 3 4 5])
; This is a comment
(def the-string "Strings use double quotes")

We can return an anonymous function with fn. The first argument is a vector of identifiers to bind the parameters to (remember that everything after the first element is are arguments)

1
(fn [a b] (+ a b))

Of course we can associate that with a symbol:

1
2
(def the-fn (fn [a b] (+ a b)))
(the-fn 2 5) ; returns 7

We’d previously seen that we defined functions with defn. What gives?

Well, defn is nothing but a macro wrapping def and doing some of its own validation on top. We can even see how it does it by using source on the REPL:

1
(source defn)

… even if its inner workings are probably not very clear to you at this stage.

Next steps

Next we’ll probably go into what a Leiningen project looks like, so you can see it’s only made up of Clojure structures, as well as go into the more esoteric and hard to Google for macros like ->>.


Published: 2017-01-23

Author

...
Ricardo J. Méndez