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.
Remember vectors?
1 | [1 2 "tres"] ; This is a valid vector with three 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 | 1 ; This is an integer |
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.
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 | {1 "One" |
We get stuff out of a hash map with get
.
1 | (get {:a 1 :b 2 :c 19 } :c) ; returns 19 |
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}) |
If you see def
, that means someone’s binding a value to an identifier.
1 | (def the-value 2) |
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 | (def the-fn (fn [a b] (+ a b))) |
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 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