“要求”中父母和括号之间有什么区别?

时间:2013-04-09 14:48:27

标签: clojure

我有点困惑的一件事是clojure require语句中的parens和括号之间的区别。我想知道是否有人可以向我解释这一点。例如,这些做同样的事情:

(ns sample.core
  (:gen-class)
  (:require clojure.set clojure.string))

 (ns sample.core
  (:gen-class)
  (:require [clojure.set] 
            [clojure.string]))

但是,这可以从repl

开始
(require 'clojure.string 'clojure.test)

但在clj文件中失败

(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)
    ....

而这些apear做同样的事情:

(ns sample.core
  (:gen-class)
  (require clojure.set clojure.string))

(ns sample.core
  (:gen-class)
  (:require clojure.set clojure.string))

总的来说,我不理解这一点。我理解使用,导入和要求。但是我不理解“:”以及[]和'()等中的事物之间的差异。任何人都能以直观的方式阐明这个话题吗?

2 个答案:

答案 0 :(得分:12)

这里的问题很微妙,如果没有先了解一下宏,可能很难理解。

宏以与函数操作值相同的方式处理语法。事实上,宏只是带钩子的函数,导致它们在编译时被评估。它们将传递您在源代码中看到的数据文字,并自上而下进行评估。让我们创建一个具有相同主体的函数和宏,以便您可以看到差异:

(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!")

宏被其返回值替换。您可以使用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}))

此时,我应该解释'导致以下形式为quote d。这意味着编译器将读取表单,但不执行它或尝试解析符号等。即'conj计算符号,而conj计算为函数。 (eval 'conj)相当于(eval (quote conj))相当于conj

考虑到这一点,请知道您无法将符号解析为命名空间,直到它以某种方式神奇地导入您的命名空间。这就是require函数的作用。它接受符号并找到它们对应的命名空间,使它们在当前命名空间中可用。

让我们看看ns宏扩展到的内容:

(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)))

了解它如何为我们引用符号clojure.setclojure.string?多方便啊!但是当您使用require代替: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)))

似乎无论是谁编写ns宏都足够让我们两种方式都这样做,因为这个结果和以前完全一样。 NEATO!

编辑:tvachon只使用: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])))

原来他们也被引用了,就像我们写require的独立调用一样。

事实证明,ns并不关心我们是否给它使用列表(parens)或向量(括号)。它只是把论点视为事物的序列。例如,这有效:

(ns sample.core
  [:gen-class]
  [:require [clojure.set]
            [clojure.string]])
正如评论中的amalloy所指出的那样,

require对于向量和列表有不同的语义,所以不要混淆它们!

最后,为什么以下不起作用?

(ns sample.core
  (:require 'clojure.string 'clojure.test))

好吧,既然ns为我们引用了这些符号,这些符号会被引用两次,这在语义上不同于仅被引用一次并且也是纯粹的疯狂。

conj    ; => #<core$conj clojure.core$conj@d62a05c>
'conj   ; => conj 
''conj  ; => (quote conj)
'''conj ; => (quote (quote conj))

我希望这会有所帮助,我绝对建议学习如何编写宏。他们非常有趣。

答案 1 :(得分:4)

TL; DR:

(ns sample.core
  (:gen-class)
  (:require clojure.set clojure.string))

 (ns sample.core
  (:gen-class)
  (:require [clojure.set] 
            [clojure.string]))

都很好 - 第二个版本只是最灵活的require语法支持的特例。这也可以写成:

 (ns sample.core
  (:gen-class)
  (:require [clojure set string]))

一般而言,最后一种形式是针对此特定要求的最佳做法。


(require 'clojure.string 'clojure.test)

也适用于clj文件 - 试试这个:

(ns sample.core
  (:gen-class))
(require 'clojure.string 'clojure.test)

这里的混淆是,在你破碎的例子中,你试图在:require宏的ns子句中使用“引用的符号”。这可能不是最直观的解释,但这是它如何分解:

有两种方法可以要求其他模块requirens

require是一个获取引用表单列表的函数(需要“引用”以避免clojure查找传递给require的符号,就像它执行所有其他符号一样)。

ns是一个支持:require选项的宏。它接受此选项的值,并在封面下将其转换为对require函数的调用。您不需要引用:require选项的值,因为ns是一个宏,因此能够引用符号本身。

这可能仍然不清楚,但我建议转向Clojure文档澄清 - 一旦你完全理解了所有你将更好地理解Clojure一般。

在Clojure源文件中,您应始终使用ns子句来要求库 - require只应在REPL中使用。


在最后两个例子中,你是正确的

(ns sample.core
  (:gen-class)
  (require clojure.set clojure.string))

有效,但这是一个意外 - 可能是因为

的结果
(name :require)
=> "require"

(name 'require)
=> "require"

记录的语法是

(ns sample.core
  (:gen-class)
  (:require clojure.set clojure.string))

并且是唯一保证未来不会破产的人。