42 ; integer
-1.5 ; floating point
22/7 ; ratio
以下是 Clojure 中常見基本型別的字面量表示範例。這些字面量都是有效的 Clojure 表達式。
;
會建立一行尾註解。有時會使用多個分號來表示標頭註解區段,但這只是一個慣例。
42 ; integer
-1.5 ; floating point
22/7 ; ratio
整數在範圍內時會讀取為固定精度的 64 位元整數,否則為任意精度。可以使用尾碼 N
來強制使用任意精度。Clojure 也支援 Java 語法,用於八進位 (字首 0
)、十六進位 (字首 0x
) 和任意進位 (字首為基數,然後是 r
,例如 2r
表示二進位) 整數。比率會提供為其自己的型別,結合分子和分母。
浮點值讀取為雙精度 64 位元浮點數,或具有 M
後綴的任意精度。也支援指數表示法。特殊符號值 ##Inf
、##-Inf
和 ##NaN
分別代表正無限大、負無限大,以及「非數字」值。
"hello" ; string
\e ; character
#"[0-9]+" ; regular expression
字串包含在雙引號中,且可能跨多行。個別字元以反斜線開頭表示。有一些特殊命名字元:\newline
\space
\tab
等。Unicode 字元可以用 \uNNNN
表示,或用 \oNNN
表示八進位。
文字正規表示法是字串,開頭為 #
。這些會編譯成 java.util.regex.Pattern 物件。
map ; symbol
+ ; symbol - most punctuation allowed
clojure.core/+ ; namespaced symbol
nil ; null value
true false ; booleans
:alpha ; keyword
:release/alpha ; keyword with namespace
符號由字母、數字和其他標點符號組成,用於參考其他東西,例如函式、值、命名空間等。符號可以選擇具有命名空間,用正斜線與名稱分隔。
有三個特殊符號讀取為不同類型 - nil
是 null 值,而 true
和 false
是布林值。
關鍵字以冒號開頭,且總是評估為它們自己。它們在 Clojure 中經常用作列舉值或屬性名稱。
接下來,我們將考慮 Clojure 如何讀取和評估表達式。
在 Clojure 中,原始碼由 讀取器 以字元形式讀取。讀取器可以從 .clj 檔案讀取原始碼,或以互動方式給定一系列表達式。讀取器會產生 Clojure 資料。Clojure 編譯器然後會產生 JVM 的位元組碼。
這裡有兩個重點
原始碼的單位是Clojure 表達式,而不是 Clojure 原始碼檔案。原始碼檔案會讀取為一系列表達式,就像你在 REPL 中互動式輸入這些表達式一樣。
將 Reader 和 Compiler 分開是一個關鍵的區分,為巨集騰出空間。巨集是將程式碼(作為資料)作為輸入,並輸出程式碼(作為資料)的特殊函式。你看得出來巨集擴充的迴圈可以在評估模型中插入在哪裡嗎?
考慮一個 Clojure 表達式
此圖說明了綠色中的語法(Reader 產生的 Clojure 資料結構)和藍色中的語意(Clojure 執行時期如何理解該資料)之間的差異。
大多數的 Clojure 文字形式會評估為它們自己,除了符號和清單。符號用於參照其他東西,並在評估時傳回它們所參照的內容。清單(如圖所示)會評估為呼叫。
在圖中,(+ 3 4) 被讀取為包含符號 (+) 和兩個數字(3 和 4)的清單。第一個元素(找到 + 的地方)可以稱為「函式位置」,也就是一個用來找到要呼叫的東西的地方。雖然函式是呼叫的明顯對象,但執行時期也有一些已知的特殊運算子、巨集和一些其他可呼叫的對象。
考慮上述表達式的評估
3 和 4 評估為它們自己(長整數)
+ 評估為一個實作 +
的函式
評估清單會呼叫 +
函式,並將 3 和 4 作為引數
許多語言同時有陳述式和表達式,其中陳述式有一些狀態效果,但不會傳回值。在 Clojure 中,所有東西都是評估為值的表達式。有些表達式(但不是大多數)也會有副作用。
現在讓我們考慮如何在 Clojure 中互動式評估表達式。
有時暫停評估很有用,特別是對於符號和清單。有時符號應該只是一個符號,而不查詢它所參照的內容
user=> 'x
x
有時清單應該只是一個資料值清單(而不是要評估的程式碼)
user=> '(1 2 3)
(1 2 3)
你可能會看到的其中一個令人困惑的錯誤是意外嘗試將資料清單評估為程式碼
user=> (1 2 3)
Execution error (ClassCastException) at user/eval156 (REPL:1).
class java.lang.Long cannot be cast to class clojure.lang.IFn
現在,不要太擔心引用,但你會偶爾在這些教材中看到它,以避免評估符號或清單。
大多數時候,當你使用 Clojure 時,你會在編輯器或 REPL(讀取-評估-列印-迴圈)中執行。REPL 有以下部分
讀取一個表達式(字元串)以產生 Clojure 資料。
評估從 #1 回傳的資料以產生結果(也是 Clojure 資料)。
透過將資料從資料轉換回字元來列印結果。
迴圈回開頭。
#2 的一個重要面向是 Clojure 總是在執行表達式之前編譯它;Clojure 總是編譯成 JVM 位元組碼。Clojure 沒有解釋器。
user=> (+ 3 4)
7
上面的方塊示範評估一個表達式 (+ 3 4) 並接收結果。
大多數 REPL 環境都支援一些技巧來協助互動使用。例如,一些特殊符號會記住評估最後三個表達式的結果
*1
(最後一個結果)
*2
(兩個表達式前的結果)
*3
(三個表達式前的結果)
user=> (+ 3 4)
7
user=> (+ 10 *1)
17
user=> (+ *1 *2)
24
此外,標準 Clojure 函式庫中包含一個命名空間 clojure.repl
,它提供許多有用的函式。若要載入那個函式庫並讓其函式在我們目前的內容中可用,請呼叫
(require '[clojure.repl :refer :all])
現在,你可以將它視為一個神奇咒語。噗!當我們進入命名空間時,我們會解開它。
我們現在可以存取在 REPL 中有用的其他函式:doc
、find-doc
、apropos
、source
和 dir
。
doc
函式會顯示任何函式的文件。讓我們在 +
上呼叫它
user=> (doc +)
clojure.core/+
([] [x] [x y] [x y & more])
Returns the sum of nums. (+) returns 0. Does not auto-promote
longs, will throw on overflow. See also: +'
doc
函式會列印 +
的文件,包括有效的簽章。
doc 函式會列印文件,然後回傳 nil 作為結果 - 你會在評估輸出中看到兩者。
我們也可以對 doc
本身呼叫
user=> (doc doc)
clojure.repl/doc
([name])
Macro
Prints documentation for a var or special form given its name
不確定某個東西叫什麼?你可以使用 apropos
指令來尋找符合特定字串或正規表示式的函式。
user=> (apropos "+")
(clojure.core/+ clojure.core/+')
你也可以使用 find-doc
來擴大搜尋範圍,包含文件字串本身
user=> (find-doc "trim")
clojure.core/subvec
([v start] [v start end])
Returns a persistent vector of the items in vector from
start (inclusive) to end (exclusive). If end is not supplied,
defaults to (count vector). This operation is O(1) and very fast, as
the resulting vector shares structure with the original and no
trimming is done.
clojure.string/trim
([s])
Removes whitespace from both ends of string.
clojure.string/trim-newline
([s])
Removes all trailing newline \n or return \r characters from
string. Similar to Perl's chomp.
clojure.string/triml
([s])
Removes whitespace from the left side of string.
clojure.string/trimr
([s])
Removes whitespace from the right side of string.
如果你想看到特定命名空間中函式的完整清單,你可以使用 dir
函式。我們可以在這裡對 clojure.repl
命名空間使用它
user=> (dir clojure.repl)
apropos
demunge
dir
dir-fn
doc
find-doc
pst
root-cause
set-break-handler!
source
source-fn
stack-element-str
thread-stopper
最後,我們不僅可以看到文件,還可以查看執行時期可存取的任何函式的底層來源
user=> (source dir)
(defmacro dir
"Prints a sorted directory of public vars in a namespace"
[nsname]
`(doseq [v# (dir-fn '~nsname)]
(println v#)))
在你進行這個工作坊時,請隨時檢視你正在使用的函式的文件字串和來源。探索 Clojure 函式庫本身的實作是進一步瞭解這門語言及其使用方式的絕佳方法。
在你學習 Clojure 時,保留一份 Clojure 參考手冊 的副本也是一個絕佳的主意。參考手冊將標準函式庫中可用的函式分類,是一個非常有價值的參考。
現在讓我們來考慮一些 Clojure 基礎,讓您開始…。
def
當您在 REPL 中評估事物時,儲存一段資料以供稍後使用會很有用。我們可以使用 def
來執行此操作
user=> (def x 7)
#'user/x
def
是一種特殊形式,它將當前命名空間中的符號 (x) 與值 (7) 關聯起來。此連結稱為 var
。在大多數實際的 Clojure 程式碼中,變數應參考常數值或函式,但在 REPL 中工作時,通常會定義和重新定義它們以方便使用。
請注意,上面的回傳值是 #'user/x
- 那是變數的文字表示:#'
後面接命名空間符號。user
是預設命名空間。
回想一下符號是透過檢索它們所指涉的內容來評估的,因此我們可以使用符號來取回值
user=> (+ x x)
14
在學習語言時最常做的事情之一就是列印值。Clojure 提供了幾個函式來列印值
對於人類 | 可讀取為資料 | ||
---|---|---|---|
有換行 |
println |
prn |
|
沒有換行 |
pr |
人類可讀的形式會將特殊列印字元(例如換行符和 tab 鍵)轉換為其列印形式,並省略字串中的引號。我們經常使用 println
來偵錯函式或在 REPL 中列印值。println
會接收任意數量的引數,並在每個引數的列印值之間插入一個空格
user=> (println "What is this:" (+ 1 2))
What is this: 3
println 函式具有副作用(列印),並回傳 nil 作為結果。
請注意,上面的「這是什麼:」並沒有列印周圍的引號,也不是讀取器可以再次讀取為資料的字串。
為此,請使用 prn 列印為資料
user=> (prn "one\n\ttwo")
"one\n\ttwo"
現在,列印的結果是一個有效的形式,讀取器可以再次讀取。根據上下文,您可能偏好人類形式或資料形式。