#?(:clj (Clojure expression)
:cljs (ClojureScript expression)
:cljr (Clojure CLR expression)
:default (fallthrough expression))
讀取條件在 Clojure 1.7 中加入。它們旨在讓 Clojure 的不同方言共用大多數與平台無關的共用程式碼,但包含一些與平台相關的程式碼。如果您撰寫跨多個平台的程式碼,而這些程式碼大多數與平台無關,則應改為分開 .clj
和 .cljs
檔案。
讀取條件已整合到 Clojure 讀取器中,不需要任何額外的工具。要使用讀取條件,您只需要讓您的檔案有 .cljc
副檔名。讀取條件是表達式,可以像一般的 Clojure 表達式一樣操作。如需更多技術細節,請參閱 讀取器 的參考頁面。
讀取條件式有兩種,標準和拼接。標準讀取條件式的行為類似於傳統的 cond
。使用語法為 #?
,如下所示
#?(:clj (Clojure expression)
:cljs (ClojureScript expression)
:cljr (Clojure CLR expression)
:default (fallthrough expression))
平台標籤 :clj
等是一組固定標籤,硬編碼到每個平台中。:default
標籤是一個眾所周知的標籤,用於在沒有平台標籤匹配時捕獲並提供表達式。如果沒有標籤匹配且未提供 :default
,則讀取條件式不會讀取任何內容(不是 nil,而是好像根本沒有從串流中讀取任何內容一樣)。
拼接讀取條件式的語法為 #?@
。它用於將清單拼接至包含的表單中。因此 Clojure 讀取器會讀取以下內容
(defn build-list []
(list #?@(:clj [5 6 7 8]
:cljs [1 2 3 4])))
如下所示
(defn build-list []
(list 5 6 7 8))
需要注意的一件重要事情是,在 Clojure 中,拼接條件式讀取器不能用於拼接多個頂層表單。具體來說,這表示您不能執行以下操作
;; Don't do this!, will throw an error
#?@(:clj
[(defn clj-fn1 [] :abc)
(defn clj-fn2 [] :cde)])
;; CompilerException java.lang.RuntimeException: Reader conditional splicing not allowed at the top level.
相反,您需要分別包裝每個函式
#?(:clj (defn clj-fn1 [] :abc))
#?(:clj (defn clj-fn2 [] :cde))
或使用 do
來包裝所有頂層函式
#?(:clj
(do (defn clj-fn1 [] :abc)
(defn clj-fn2 [] :cde)))
讓我們瀏覽一些您可能想要使用這些新讀取條件式的地方的範例。
主機互通是讀取條件式解決的最大痛點之一。您可能有一個幾乎是純 Clojure 的 Clojure 檔案,但需要呼叫主機環境以取得一個函式。 這是一個經典範例
(defn str->int [s]
#?(:clj (java.lang.Integer/parseInt s)
:cljs (js/parseInt s)))
命名空間是 Clojure 和 ClojureScript 之間共享程式碼的另一個大痛點。ClojureScript 對於 需要巨集有不同於 Clojure 的語法。要在 .cljc
檔案中使用在 Clojure 和 ClojureScript 中都能運作的巨集,您需要在命名空間宣告中使用讀取條件式。
以下是在 測試 中來自 route-ccrs 的範例
(ns route-ccrs.schema.ids.part-no-test
(:require #?(:clj [clojure.test :refer :all]
:cljs [cljs.test :refer-macros [is]])
#?(:cljs [cljs.test.check :refer [quick-check]])
#?(:clj [clojure.test.check.properties :as prop]
:cljs [cljs.test.check.properties :as prop
:include-macros true])
[schema.core :as schema :refer [check]]))
以下為另一個範例,我們希望能夠在 Clojure 和 ClojureScript 中使用 rethinkdb.query
命名空間。不過,我們無法在 ClojureScript 中載入所需的 rethinkdb.net
,因為它使用 Java socket 與資料庫通訊。相反,我們使用讀取條件式,因此只有在 Clojure 程式讀取時才會需要命名空間。
(ns rethinkdb.query
(:require [clojure.walk :refer [postwalk postwalk-replace]]
#?(:clj [rethinkdb.net :as net])))
;; snip...
#?(:clj (defn run [query conn]
(let [token (get-token conn)]
(net/send-start-query conn token (replace-vars query)))))
例外處理是另一個受益於讀取條件的區域。ClojureScript 支援 (catch :default)
來捕捉所有內容,但是您通常仍會想要處理特定主機的例外。以下是一個來自 lemon-disc 的 範例。
(defn message-container-test [f]
(fn [mc]
(passed?
(let [failed* (failed mc)]
(try
(let [x (:data mc)]
(if (f x) mc failed*))
(catch #?(:clj Exception :cljs js/Object) _ failed*))))))
拼接讀取條件不像標準條件那樣廣泛使用。關於其使用方式的範例,我們來看 ClojureCLR 讀取器中讀取條件的 測試。乍看之下可能不明顯的是,拼接讀取條件內的向量會被包覆在一個周圍向量中。
(deftest reader-conditionals
;; snip
(testing "splicing"
(is (= [] [#?@(:clj [])]))
(is (= [:a] [#?@(:clj [:a])]))
(is (= [:a :b] [#?@(:clj [:a :b])]))
(is (= [:a :b :c] [#?@(:clj [:a :b :c])]))
(is (= [:a :b :c] [#?@(:clj [:a :b :c])]))))
對於要將 .cljc
檔案放在哪裡,社群目前還沒有明確的共識。兩個選項是在具有 .clj
、.cljs
和 .cljc
檔案的單一 src
目錄中,或是在分開的 src/clj
、src/cljc
和 src/cljs
目錄中。
在讀取條件被引入之前,透過名為 cljx 的 Leiningen 外掛程式解決了在平台之間共用程式碼的相同目標。cljx 處理具有 .cljx
副檔名的檔案,並將多個特定於平台的檔案輸出到產生的來源目錄。然後,Clojure 讀取器 會將它們讀取為正常的 Clojure 或 ClojureScript 檔案。這運作良好,但需要執行另一個工具組件。cljx 在 2015 年 6 月 13 日被棄用,改用讀取條件。
Sente 以前使用 cljx 在 Clojure 和 ClojureScript 之間共用程式碼。我已改寫 main 命名空間以使用讀取條件。請注意,我們已使用拼接讀取條件將向量拼接至父 :require
。另請注意,某些需要在 :clj
和 :cljs
之間重複。
(ns taoensso.sente
(:require
#?@(:clj [[clojure.string :as str]
[clojure.core.async :as async]
[taoensso.encore :as enc]
[taoensso.timbre :as timbre]
[taoensso.sente.interfaces :as interfaces]]
:cljs [[clojure.string :as str]
[cljs.core.async :as async]
[taoensso.encore :as enc]
[taoensso.sente.interfaces :as interfaces]]))
#?(:cljs (:require-macros
[cljs.core.async.macros :as asyncm :refer (go go-loop)]
[taoensso.encore :as enc :refer (have? have have-in)])))
(ns taoensso.sente
#+clj
(:require
[clojure.string :as str]
[clojure.core.async :as async)]
[taoensso.encore :as enc]
[taoensso.timbre :as timbre]
[taoensso.sente.interfaces :as interfaces])
#+cljs
(:require
[clojure.string :as str]
[cljs.core.async :as async]
[taoensso.encore :as enc]
[taoensso.sente.interfaces :as interfaces])
#+cljs
(:require-macros
[cljs.core.async.macros :as asyncm :refer (go go-loop)]
[taoensso.encore :as enc :refer (have? have have-in)]))
原始作者:Daniel Compton