2013-04-09 68 views
15

我一直有点困惑的是clojure需要语句中的parens和括号之间的区别。我想知道是否有人可以向我解释这一点。例如,这些做同样的事情:“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)) 

一般来说,我不明白这一点。我了解使用,导入和要求。但是我不明白“:”和[]和'()等等之间的区别。任何人都可以用直观的方式阐明这个话题吗?

+0

可能的重复[为什么要求在ns形式表现不同于require函数](http://stackoverflow.com/questions/3719929/why-does-require-in-the-ns-form-b​​ehave-与需求函数不同) – 2013-04-09 14:51:29

+1

嗯,并没有真正询问[]和repl和clj代码之间的区别。 – 2013-04-09 15:07:00

回答

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 [email protected]))) ; 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]]) 

require,如在评论amalloy指出,有载体,并列出不同的语义,所以不要混用这些了!

最后,为什么以下不工作?

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

好吧,既然ns确实我们的报价对我们来说,这些符号得到引述两次,这是被引述只有一次语义不同,也是纯粹的疯狂。

conj ; => #<core$conj [email protected]> 
'conj ; => conj 
''conj ; => (quote conj) 
'''conj ; => (quote (quote conj)) 

我希望这可以帮助,我绝对推荐学习如何编写宏。他们超级好玩。

+2

'(:require(clojure.set)(clojure.string))'根本不起作用。这是一个无操作,它看起来像是因为你选择了两个已经需要的命名空间。尝试一些不存在的命名空间:它默默成功;在现存的命名空间中,它默默无闻。在这里使用parens表示前缀列表,如'(:require(clojure set string))';'您给出的语法仅适用于矢量。 – amalloy 2013-04-09 18:43:32

+0

斑点。我将编辑帖子以反映。 – 2013-04-09 20:01:10

+0

很好的答案和+1的TDT参考,如果这就是它是什么 – Hendekagon 2013-04-10 00:10:14

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) 

这里的困惑是,在你的破例如你想在使用“援引符号” ns宏的子句:require。这可能不是最直观的解释,但这里是它如何分解:

有两种方法需要其他模块,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)) 

是唯一一个保证不会在未来破产的人。

+0

“在Clojure源文件中,您应始终使用ns子句来要求库 - require只能在REPL中使用。”为什么? – 2013-04-09 16:50:43

+2

没有技术原因 - 完全是一种风格和可读性。使用'ns'始终确保其他程序员可以轻松查看文件所需的命名空间,而无需查看整个文件。这也是一个更清洁,因为你不需要手动转义表单。 – tvachon 2013-04-09 17:35:53

相关问题