The API Cheatsheet section on Lists seems to indicate that '()
is a list constructor, just like (list)
, but I've found that in practice they're not exactly the same. For example, given:
(def foo "a")
(def bar "b")
(def zip "c")
The following statement:
(apply str '(foo bar zip))
produces the output "foobarzip", which I wouldn't expect.
But the supposedly equivalent:
(apply str (list foo bar zip))
produces "abc", as I'd expect.
What's going on here? If there's a "shorthand" for a list in Clojure (like {}
for maps and []
for vectors), what is it?
In lisps, '
(like quote
) quotes its arguments, i.e. preserves them pretty much exactly as written in their s-exp form, including not evaluating anything within.
To put it another way '(foo bar zip)
creates a list containing the symbols foo
, bar
, zip
; while (list foo bar zip)
creates a list containing the values of foo
, bar
, zip
. In the first case, str
will be converting the symbols themselves to strings and then concatenating them.
As a demonstration of this:
=> (def foo "a")
=> (type (first '(foo)))
clojure.lang.Symbol
=> (type (first (list foo)))
java.lang.String
The difference is that, as you can see, the literal syntax '()
is quoted. This means symbols inside are not evaluated. To use list literals while evaluating the elements you can exploit the syntax-quote
reader macro:
user=> (apply str `(~foo ~bar ~zip))
"abc"
When you write:
(def foo "a")
(def bar "b")
(def zip "c")
You've defined three symbols: foo
, bar
and zip
associated with values: "a"
, "b"
and "c"
.
The association is stored inside the namsepace table, so if using the REPL, the namespace would be user
by default, so the user namespace table would now contain:
{foo
#'user/foo
bar
#'user/bar,
zip
#'user/zip}
You can see the namespace table by doing: (ns-interns *ns*)
Now if you write: (foo bar zip)
inside Clojure, there is going to be four different ways this can be read by the reader. You'll need to specify to the reader which way it should be read. That's where `
, '
and list
come into play.
Case of no indicator:
(foo bar zip)
When simply written without any indicator, the reader will interpret this as an S-expression and will interpret foo
as a symbol mapping to a function, with bar
and zip
as symbols mapping to values to be passed into the foo
function.
In our case, it will throw an exception:
java.lang.ClassCastException: java.lang.String cannot be cast to clojure.lang.IFn
This is because no function foo
was defined, foo
was associated with "a"
, which is a String, not an IFn (a Clojure function).
If the function foo
was defined, it would have called foo
passing in as argument "b"
and "c"
.
Case of list
:
(list foo bar zip)
When using the list symbol, the reader is actually interpreting this the same way as the no indicator case. That is, it's looking for a symbol list
that maps to a function which will take the associated values mapped to foo
, bar
and zip
as arguments. The list
function is pre-defined by Clojure inside the clojure.core namespace; it returns a list of it's arguments.
So when the reader looks for list
, it find the clojure.core function, then it looks for the foo
symbol, and finds that it maps to "a"
, and so on. Once it has found all the mapping for the symbols, it calls list
and passes it the associated values of foo bar zip
, which would be "a" "b" "c"
. So (list foo bar zip)
is the same as (list "a" "b" "c")
.
Case of '
:
'(foo bar zip)
The '
quote tells the reader that the form that follows is to be read as is. In our case, foo
, bar
and zip
are symbols, and (foo bar zip)
is a list of symbols. So the reader will interpret this as a list of symbols.
That's why when you run (apply str '(foo bar zip))
it's going to call str 'foo 'bar 'zip
which will give you foobarzip
. That is, it's going to convert each symbol in the list of symbols to a String representation, and then concatenate these into one String.
By taking the form as is, it's passing as argument a list of symbols, without evaluating the symbols, i.e., without looking for what they're associated with. If you ran (eval '(foo bar zip))
you would pass a list of symbols to eval
, and eval
would evaluate the symbols to values and return a list of the values the symbols are mapped to. So you can think of the quote '
as passing the code around as code.
Case of `
:
`(foo bar zip)
This one is a little more complicated. The reader will see the backquote `
and will resolve the symbols inside the list of symbols recursively as to get a list of fully qualified symbols.
Basically, when the reader looks up symbols inside the table of symbols, it does so from the table of symbols of the current namespace. A fully qualified symbol, is a symbol that includes the namespace information. So when running `(foo bar zip)
the reader will replace those symbols with fully qualified ones, turning it into (user.foo user.bar user.zip)
.
It's possible to tell the reader to evaluate some on the elements in the list, while changing others to fully qualified symbols. To do so, you prefix the symbols you want evaluated with ~
as in:
`(foo ~bar zip)
will give you
(clojure.foo "b" clojure.zip)
Effectively, the backquote `
is a lot similar to the quote '
in that it does not evaluate, but simply returns code, except that it manipulates the code to be returned a bit by fully qualifying symbols within it. This has implications for macros, where sometimes you might want a fully qualified reference, to fetch from another namespace, and sometimes you want flexibility in saying, look for this symbol in the current namespace.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With