2013-04-09 69 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

相關問題