## Clojure 語法
Lisp方言有一個非常簡潔的語法 — 有些人覺得很美的語法。數據和代碼的表達形式是一樣的,一個列表的列表很自然地在內存里面表達成一個tree。(a b c)表示一個對函數a的調用,而參數是b和c。如果要表示數據,你需要使用 `'(a b c)` o或者 `(quote (a b c))` 。通常情況下就是這樣了,除了一些特殊情況 — 到底有多少特殊情況取決于你所使用的方言。
我們把這些特殊情況稱為語法糖。語法糖越多代碼寫起來越簡介,但是同時我們也要學習更多的東西以讀懂這些代碼。這需要找到一個平衡點。很多語法糖都有對應的函數可以調用。到底語法糖是多了還是少了還是你們自己來判斷吧。
下面這個表格簡要地列舉了Clojure里面的一些語法糖, 這些語法糖我們會在后面詳細講解的,所以如果你現在沒辦法完全理解它們不用擔心。
| 作用 | 語法糖 | 對應函數 |
| --- | --- | --- |
| 注釋 | `; _text_` ?_單行注釋_ | `宏(comment _text_)可以用來寫多行注釋` |
| 字符 (Java `char` 類型) | `\_char_` `\tab` `\newline` `\space` `\u_unicode-hex-value_` | `(char _ascii-code_)` `(char \u_unicode_` ) |
| 字符串 (Java `String` 對象) | `"_text_"` | `(str _char1_ _char2_ ...)` 可以把各種東西串成一個字符串 |
| 關鍵字是一個內部字符串; 兩個同樣的關鍵字指向同一個對象; 通常被用來作為map的key | `:_name_` | `(keyword "_name_")` |
| 當前命名空間的關鍵字 | `::_name_` | N/A |
| 正則表達式 | `#"_pattern_"` | `(re-pattern _pattern_)` |
| 逗號被當成空白(通常用在集合里面用來提高代碼可讀性) | `,` (逗號) | N/A |
| 鏈表(linked list) | `'(_items_)` (不會evaluate每個元素) | `(list _items_)` 會evaluate每個元素 |
| vector(和數組類似) | `[_items_]` | `(vector _items_)` |
| set | `#{_items_}` 建立一個hash-set | `(hash-set _items_)` `(sorted-set _items_)` |
| map | `{_key-value-pairs_}` 建立一個hash-map | `(hash-map _key-value-pairs_)` `(sorted-map _key-value-pairs_)` |
| 給symbol或者集合綁定元數據 | `#^{_key-value-pairs_} _object_` 在讀入期處理 | `(with-meta _object_ _metadata-map_)` 在運行期處理 |
| 獲取symbol或者集合的元數據 | `^_object_` | `(meta _object_)` |
| 獲取一個函數的參數列表(個數不定的) | `& _name_` | N/A |
| 函數的不需要的參數的默認名字 | `_` (下劃線) | N/A |
| 創建一個java對象(注意class-name后面的點) | `(_class-name_. _args_)` | `(new _class-name_ _args_)` |
| 調用java方法 | `(. _class-or-instance_ _method-name_ _args_)` 或者 `(._method-name_ _class-or-instance_ _args_)` | N/A |
| 串起來調用多個函數,前面一個函數的返回值會作為后面一個函數的第一個參數;你還可以在括號里面指定額外參數;注意前面的兩個點 | `(.. _class-or-object_ (_method1 args_) (_method2 args_) ...)` | N/A |
| 創建一個匿名函數 | `#(_single-expression_)` 用 `%` (等同于 `%1` ), `%1` , `%2來表示參數` | `(fn [_arg-names_] _expressions_)` |
| 獲取Ref, Atom 和Agent對應的valuea | `@_ref_` | `(deref _ref_)` |
| get `Var` object instead of the value of a symbol (var-quote) | `#'_name_` | `(var _name_)` |
| syntax quote (使用在宏里面) | ``` | none |
| unquote (使用在宏里面) | `~_value_` | `(unquote _value_)` |
| unquote splicing (使用在宏里面) | `~@_value_` | none |
| auto-gensym (在宏里面用來產生唯一的symbol名字) | `_prefix_#` | `(gensym _prefix_ )` |
對于二元操作符比如 +和*, Lisp方言使用前置表達式而不是中止表達式,這和一般的語言是不一樣的。比如在java里面你可能會寫 `a + b + c` , 而在Lisp里面它相當于 `(+ a b c) 。這種表達方式的一個好處是如果操作數有多個,那么操作符只用寫一次` . 其它語言里面的二元操作符在lisp里面是函數,所以可以有多個操作數。
Lisp代碼比其它語言的代碼有更多的小括號的一個原因是Lisp里面不使用其它語言使用的大括號,比如在java里面,方法代碼是被包含在大括號里面的,而在lisp代碼里面是包含在小括號里面的。
比較下面兩段簡單的Java和Clojure代碼,它們實現相同的功能。它們的輸出都是: “edray” 和 “orangeay”.
```
// This is Java code.
public class PigLatin {
public static String pigLatin(String word) {
char firstLetter = word.charAt(0);
if ("aeiou".indexOf(firstLetter) != -1) return word + "ay";
return word.substring(1) + firstLetter + "ay";
}
public static void main(String args[]) {
System.out.println(pigLatin("red"));
System.out.println(pigLatin("orange"));
}
}
```
```
; This is Clojure code.
; When a set is used as a function, it returns a boolean
; that indicates whether the argument is in the set.
(def vowel? (set "aeiou"))
(defn pig-latin [word] ; defines a function
; word is expected to be a string
; which can be treated like a sequence of characters.
(let [first-letter (first word)] ; assigns a local binding
(if (vowel? first-letter)
(str word "ay") ; then part of if
(str (subs word 1) first-letter "ay")))) ; else part of if
(println (pig-latin "red"))
(println (pig-latin "orange"))
```
Clojure支持所有的常見數據類型比如 booleans ( `true` and `false` ), 數字, 高精度浮點數, 字符(上面表格里面提到過 ) 以及字符串. 同時還支持分數 — 不是浮點數,因此在計算的過程中不會損失精度.
Symbols是用來給東西命名的. 這些名字是被限制在名字空間里面的,要么是指定的名字空間,要么是當前的名字空間. Symbols的值是它所代表的名字的值. 要使用Symbol的值,你必須把它用引號引起來.
關鍵字以冒號打頭,被用來當作唯一標示符,通常用在map里面 (比如 `:red` , `:green` 和 `:blue` ).
和任何語言一樣,你可以寫出很難懂的Clojure代碼。遵循一些最佳實踐可以避免這個。寫一些簡短的,專注自己功能的函數可以使函數變得容易讀,測試以及重復利用。經常使用“抽取方法”的模式來對你的代碼進行重構。高度內嵌的函數是非常難懂得,千萬不要這么寫, 你可以使用let來幫助你。把匿名函數傳遞給命名函數是非常常見的,但是不要把一個匿名函數傳遞給另外一個匿名函數, 這樣代碼就很難懂了。