(ns foo.core)
(defmacro add
[a b]
`(+ ~a ~b))
ClojureScript 的 ns
形式在大多數用法中可以非常簡單且類似於 Clojure,尤其是最近添加到 ClojureScript 的功能。
但是,ns
形式底下有一組豐富的選項,您可能會在實際應用中遇到它們。本指南旨在逐步介紹這些選項並提供一些說明。
在 ClojureScript 中,巨集的處理方式與 Clojure 有些不同。特別是,ns
形式支援 :require-macros
,以及一些簡化的語法糖和隱式載入行為,我們將在這裡介紹。
為了舉例說明,假設我們在 src/foo/core.clj
中有以下內容
(ns foo.core)
(defmacro add
[a b]
`(+ ~a ~b))
為了在某些 ClojureScript 原始碼中使用此巨集,您可以使用 :require-macros
規格,如下所示
(ns bar.core
(:require-macros [foo.core]))
(foo.core/add 2 3)
現在,:require-macros
的設計與 :require
非常相似。因此,例如,您可以直接將 add
符號引用到您的命名空間中
(ns bar.core
(:require-macros [foo.core :refer [add]]))
(add 2 3)
上述的另一種選擇 (不常見,但為了完整性值得介紹) 是
:use-macros
。在 ClojureScript 中,:require
/:refer
和:use
/:only
本質上是彼此的雙重形式。因此,上面的ns
形式也可以寫成(ns bar.core (:use-macros [foo.core :only [add]]))
。
您還可以設定一個命名空間別名 foo
(ns bar.core
(:require-macros [foo.core :as foo]))
(foo/add 2 3)
上面的範例都使用了 :require-macros
基本元素。
但是,通常您會使用來自同一個命名空間的程式庫,其中提供執行時程式碼 (函式和其他 def
) 和巨集。
因此,繼續我們的範例,假設有一個 src/foo/core.cljs
檔案,其中包含
(ns foo.core)
(defn subtract
[a b]
(- a b))
現在,如果您想同時使用 add 和 subtract,您可以執行類似以下的操作
(ns bar.core
(:require-macros [foo.core :refer [add]])
(:require [foo.core :refer [subtract]]))
(add 2 3)
(subtract 7 4)
但是,有一點 ns
形式的語法糖,:refer-macros
,可讓您改為寫成
(ns bar.core
(:require [foo.core :refer [subtract] :refer-macros [add]]))
(add 2 3)
(subtract 7 4)
上面的 :refer-macros
實際上只是語法糖,它會由編譯器以演算法方式解糖成之前的形式。
同樣地,有一個 :include-macros
語法糖,您可以使用它來表示應該使用與執行時命名空間相同的規格來要求巨集命名空間。因此,例如,以下程式碼可以運作
(ns bar.core
(:require [foo.core :as foo :include-macros true]))
(foo/add 2 3)
(foo/subtract 7 4)
以上會解糖為更冗長的基本形式
(ns bar.core
(:require-macros [foo.core :as foo])
(:require [foo.core :as foo]))
(foo/add 2 3)
(foo/subtract 7 4)
在執行時命名空間被要求的案例中,如果該命名空間內部需要它自己的巨集命名空間 (表示具有相同名稱的命名空間),則您會免費獲得 :include-macros
語法糖。如果您正在開發程式庫,建議使用此方法,如此使用者可以簡化他們的 require 形式。
為了說明這一點,假設我們的 src/foo/core.cljs
檔案看起來像這樣
(ns foo.core
(:require-macros foo.core))
(defn subtract
[a b]
(- a b))
現在,您可以像這樣使用程式碼
(ns bar.core
(:require [foo.core :as foo]))
(foo/add 2 3)
(foo/subtract 7 4)
那麼,這個看起來不錯的簡化呢?
(ns bar.core
(:require [foo.core :refer [add subtract]))
(add 2 3)
(subtract 7 4)
在這種情況下,add
是一個巨集,而 subtract
是一個函式的事實會由編譯器自動處理,因此可以統一引用變數,並且 ns
形式看起來基本上與在 Clojure 中相同。
如果您在 REPL 中需要快速參考上述主題,則 ns
特殊形式的文件字串可提供協助。語法糖形式稱為內聯巨集規格,而隱式語法糖稱為隱式巨集載入。文件字串中包含一個相當完整的解糖範例。在緊要關頭,(doc ns)
是您的好幫手。
require
和 require-macros
巨集您可以使用 require
和 require-macros
將程式碼動態載入到您的 REPL 中。有趣的是,上述功能也適用於這些巨集。
這是一個實作細節,但它可以幫助您了解如何完成此操作:當您發出
(require-macros '[foo.core :as foo :refer [add]])
在 REPL 中,這會在內部轉換為類似以下內容的 ns
形式
(ns cljs.user
(:require-macros [foo.core :as foo :refer [add]]))
而且,重要的是,當您使用 require
時,會使用類似的 ns
形式,並且它會受到上述所有解糖和推斷行為的影響。
clojure
命名空間別名某些命名空間 (例如 clojure.string
和 clojure.set
) 可用於 ClojureScript 中,即使這些命名空間中的第一個區段是 clojure
。但其他命名空間 (例如 cljs.pprint
、cljs.test
,以及現在的 cljs.spec
) 則位於 cljs
下。
為什麼會有這種差異?理想情況下,應該沒有差異。但是,如果您查看 ClojureScript 中使用的 clojure.pprint
連接埠,它會涉及一個巨集命名空間。這就是問題所在。由於 JVM ClojureScript 編譯器使用 Clojure 執行,如果沒有將連接埠移至 cljs.pprint
,則會發生命名空間衝突。簡而言之,clojure.pprint
命名空間已被佔用。
這樣做的結果是,我們必須記住在編寫 ClojureScript 時,針對某些命名空間使用 cljs.*
。而且,如果您正在編寫可攜式程式碼,則需要使用讀取器條件式。
ns
形式有一個相對較新的簡化功能,您可以使用它:在可以對應到 cljs.*
命名空間的不存在 clojure.*
命名空間的情況下,您可以使用 clojure
來代替命名空間的第一個區段中的 cljs
。
一個簡單的範例
(ns foo.core
(:require [clojure.test]))
可以改為使用
(ns foo.core
(:require [cljs.test]))
如果您這樣做,ClojureScript 編譯器會先查看是否可以載入 clojure.test
命名空間。由於它不存在,因此會回退到載入 cljs.test
。
同時,會從 clojure.test
設定到 cljs.test
的別名,就像您已寫入
(ns foo.core
(:require [cljs.test :as clojure.test]))
這很重要,因為它可讓您擁有限定符號的程式碼,例如 clojure.test/test-var
。
透過這個別名,以及在 :refer
規格中推斷巨集變數的能力 (請參閱上方的「隱式引用」),以下程式碼可以在 ClojureScript 中正常運作
(ns foo.core-test
(:require [clojure.test :as test :refer [deftest is]]))
(deftest foo-test
(is (= 3 4)))
(test/test-var #'foo-test)
更重要的是:這與您在 Clojure 中編寫的程式碼完全相同。無需讀取器條件式!
當然,這也適用於 require
ClojureScript 巨集。因此,例如,您可以執行
(require '[clojure.spec :as s])
然後,(s/def ::even? (s/and number? even?))
將可正常運作。這樣做的原因是 require
巨集是根據 ns
特殊形式實作的。
希望這些詳細的範例有助於釐清 ns
解糖、推斷和別名的運作方式。總體意圖是簡化 ClojureScript ns
形式的使用方式,但是解開這些額外功能如何運作可以讓您在想要或需要了解實際情況時有更好的理解。
善用這些功能應該有助於大幅減少 ClojureScript 和 Clojure ns
形式之間的差異。
原始作者:Mike Fikes