Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Whats the difference between parens and brackets in "require"?

Tags:

clojure

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?

like image 517
David Williams Avatar asked Apr 09 '13 14:04

David Williams


1 Answers

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 quoted. 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.

like image 122
d.j.sheldrick Avatar answered Sep 29 '22 19:09

d.j.sheldrick