One thing I've been a little confused about is the differences between parens and brackets in clojure require statements. I was wondering if someone could explain this to me. For example, these do the same thing:
(ns sample.core
(:gen-class)
(:require clojure.set clojure.string))
and
(ns sample.core
(:gen-class)
(:require [clojure.set]
[clojure.string]))
However, this works from the repl
(require 'clojure.string 'clojure.test)
But fails in a clj file
(ns sample.core
(:gen-class)
(:require 'clojure.string 'clojure.test))
...
Exception in thread "main" java.lang.Exception: lib names inside prefix lists must not contain periods
at clojure.core$load_lib.doInvoke(core.clj:5359)
at clojure.lang.RestFn.applyTo(RestFn.java:142)
....
Whereas these apear to do the same thing:
(ns sample.core
(:gen-class)
(require clojure.set clojure.string))
(ns sample.core
(:gen-class)
(:require clojure.set clojure.string))
In general I'm not understanding this. I understand use, import and require. But I do not understand the ":" and the differences between things in [] and '() etc. Can anyone illuminate this topic in an intuitive way?
The issue here is subtle and possibly difficult to grok without first understanding a bit about macros.
Macros manipulate syntax in the same way that functions manipulate values. In fact, macros are just functions with a hook that causes them to be evaluated at compile time. They are passed the data literal that you see in the source code and are evaluated top-down. Let's make a function and a macro that have the same body so you can see the difference:
(defmacro print-args-m [& args]
(print "Your args:")
(prn args))
(defn print-args-f [& args]
(print "Your args:")
(prn args))
(print-args-m (+ 1 2) (str "hello" " sir!"))
; Your args: ((+ 1 2) (str "hello" " sir!"))
(print-args-f (+ 1 2) (str "hello" " sir!"))
; Your args: (3 "hello sir!")
Macros are replaced by their return value. You can inspect this process with macroexpand
(defmacro defmap [sym & args]
`(def ~sym (hash-map ~@args))) ; I won't explain these crazy symbols here.
; There are plenty of good tutorials around
(macroexpand
'(defmap people
"Steve" {:age 53, :gender :male}
"Agnes" {:age 7, :gender :female}))
; (def people
; (clojure.core/hash-map
; "Steve" {:age 53, :gender :male}
; "Agnes" {:age 7, :gender :female}))
At this point, I should probably explain that '
causes the following form to be quote
d. This means that the compiler will read the form, but not execute it or try to resolve symbols and so forth. i.e. 'conj
evaluates to a symbol, while conj
evaluates to a function. (eval 'conj)
is equivalent to (eval (quote conj))
is equivalent to conj
.
With that in mind, know that you can't resolve a symbol as a namespace until it has been magically imported into your namespace somehow. This is what the require
function does. It takes symbols and finds the namespaces they correspond to, making them available in the current namespace.
Let's see what the ns
macro expands to:
(macroexpand
'(ns sample.core
(:require clojure.set clojure.string)))
; (do
; (clojure.core/in-ns 'sample.core)
; (clojure.core/with-loading-context
; (clojure.core/refer 'clojure.core)
; (clojure.core/require 'clojure.set 'clojure.string)))
See how it quoted the symbols clojure.set
and clojure.string
for us? How convenient! But what's the deal when you use require
in stead of :require
?
(macroexpand
'(ns sample.core
(require clojure.set clojure.string)))
; (do
; (clojure.core/in-ns 'sample.core)
; (clojure.core/with-loading-context
; (clojure.core/refer 'clojure.core)
; (clojure.core/require 'clojure.set 'clojure.string)))
It seems that whoever wrote the ns
macro was nice enough to let us do it both ways, since this result is exactly the same as before. Neato!
edit: tvachon is right about only using :require
since it is the only officially supported form
But what's the deal with brackets?
(macroexpand
'(ns sample.core
(:require [clojure.set]
[clojure.string])))
; (do
; (clojure.core/in-ns 'sample.core)
; (clojure.core/with-loading-context
; (clojure.core/refer 'clojure.core)
; (clojure.core/require '[clojure.set] '[clojure.string])))
Turns out they get quoted too, just like we'd do if we were writing standalone calls to require
.
It also turns out that ns
doesn't care whether we give it lists (parens) or vectors (brackets) to work with. It just sees the arguments as sequences of things. For example, this works:
(ns sample.core
[:gen-class]
[:require [clojure.set]
[clojure.string]])
require
, as pointed out by amalloy in the comments, has different semantics for vectors and lists, so don't mix those up!
Finally, why doesn't the following work?
(ns sample.core
(:require 'clojure.string 'clojure.test))
Well, since ns
does our quoting for us, these symbols get quoted twice, which is semantically different from being quoted only once and is also pure craziness.
conj ; => #<core$conj clojure.core$conj@d62a05c>
'conj ; => conj
''conj ; => (quote conj)
'''conj ; => (quote (quote conj))
I hope this helps, and I definitely recommend learning how to write macros. They're super fun.
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