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来帮助你。把匿名函数传递给命名函数是非常常见的,但是不要把一个匿名函数传递给另外一个匿名函数, 这样代码就很难懂了。