Keywords are used much as we would use a keyword in Ruby or an enum in Java, and are prepended with a colon.
| => :foo |
| :foo |
Vectors give us fast positional access to their elements. They can be created by placing the elements of the vector inside of square brackets. We create a vector and name it some-keywords in the following snippet.
| => (def some-keywords [:foo :bar :baz]) |
| #'matters/some-keywords |
We can use first to get the first element of a vector.
| => (first some-keywords) |
| :foo |
In the preceding example, the actions of the reader take place behind the scenes, as part of the REPL. Let’s make things a bit more explicit by using read-string. This takes a string directly and reads it. Here, we’re using it to read in a new vector and name it some-more-keywords.
| => (def some-more-keywords (read-string "[:foo :bar :baz]")) |
| #'matters/some-more-keywords |
We can treat it just as we did our original vector.
| => (first some-more-keywords) |
| :foo |
So far, the reader might remind you of something like JSON or YAML. It takes a string and turns it into some more complicated, probably nested, data structure. That’s not far off, but something about it might strike you as odd. Here I am claiming that the Read in REPL reads in data that we can manipulate in our code, much like JSON or YAML parser would.
But aren’t we typing code into the REPL? How does that work?
To find out, let’s take a look at another Clojure data structure, the list. In Clojure, as in other Lisps, a list is a singly linked list. One way to create a list is to use list, as we do in the following code snippet.
| => (def a-list (list :foo :bar :baz)) |
| #'matters/a-list |
Another way is to simply enclose the list elements in round braces. Here we do that using read-string this time, just as we did with our earlier vector.
| => (def another-list (read-string "(:foo :bar :baz)")) |
| #'matters/another-list |
These two lists are equivalent.
| => (first a-list) |
| :foo |
| => (first another-list) |
| :foo |
| => (= a-list another-list) |
| true |
Let’s take a look at another list. Here, we create a list with three elements: the symbol + and the integers 21 and 21.
| => (def funky-looking-list (read-string "(+ 21 21)")) |
| #'matters/funky-looking-list |
And here, we use the first function to get the first element.
| => (first funky-looking-list) |
| + |
Our first two list examples just contain keywords; our final one obviously contains code! Clojure code is just Clojure data, a property known as homoiconicity. The evaluation rule that we hinted at earlier for function calls is actually the evaluation rule for lists. We can see this by evaluating funky-looking-list manually, as we do in the following snippet.
| => (eval funky-looking-list) |
| 42 |
Because Clojure code is just Clojure data, we can manipulate it just as we would any other data. This gives us, the humble application or framework programmer, an incredible amount of power.