函数式风格、对并发的强力支持以及干净的Java互操作。
● 形式(Forms)
● 读取器宏
● 函数
● 绑定和命名空间
● 流程控制
● 元数据
Clojure语言极富表现力。
形式Clojure具有同像性,Clojure代码本身,是由Clojure数据构成的。
当运行一段Clojure程序,作为Clojure组成部分的读取器(reader),读入那些被称为“形式”的程序文本块,然后将它们翻译为Clojure的数据结构。接下来,Clojure编译并执行这些数据结构。
Clojure的形式
使用数值类型数值字面量本身就是形式。
由数字组成的向量是另外一种形式。
列表也是一种形式。一个列表可以“仅仅只是数据”,但也可以用于调用函数。
Clojure对这个列表进行求值的时候,是把它当作了一次调用。与更常见的中缀表示法(infix notation,例如:1+2=3)相对,这种将函数放到最前面的风格被称为前缀表示法(prefix notation)。
当函数名是一个单词时,前缀表示法是相当常见的。concat 作为函数名会出现在表达式的起始位置,这就符合大多数程序员的预期。
Clojure只不过是像对待所有其他函数一样,把数学运算符也简单地放到起始位置罢了。采用前缀表示法还有一个实际好处,可以很容易将其扩展为任意数量的参数。
甚至在没有参数的这种退化情况(degenerate case)下,它仍然能按照期望的那样,返回一个零。这非常有利于消除为处理边界条件而产生的特例逻辑,此类逻辑往往非常脆弱。
Clojure的许多数学和比较运算符,都具有与其他语言相同的名称和语义。加法、减法、乘法、比较和等于操作符都会按照期望的方式来工作。
比例(Ratio)类型
想要的是十进制除法,就得用浮点数的字面量来作为被除数。
需要任意精度的浮点运算时,在数字后面追加一个大写的M,就可以创建一个BigDecimal类型的字面量。
为了得到任意精度的整数,可以通过在数字后面追加一个大写的 N,来创建一个BigInt类型的字面量。
注意,算式中只需要一个BigInt字面量就够了,因为它会传染到整个计算过程当中。
符号诸如+、concat和java.lang.String这样的形式都被称为符号,用来为事物命名。
Clojure 中,符号用来对各式各样的东西命名。
● 函数,例如str和concat。
● 操作符,例如+和-,它们终究不过是函数罢了。
● Java类,例如java.lang.String和java.util.Random。
● 命名空间和Java包,例如clojure.core和java.lang。
● 数据结构和引用类型。符号不能以数字开头,但可以包含字母、数字、加号(+)、减号(-)、乘号(*)、除号(/)、感叹号(!)、问好(?)、英文句号(.)和下划线(_)。
此处列出的是Clojure承诺支持的合法符号的最小字符集。应该在自己的代码中坚持只使用这些字符,但千万不要假设其他的 Clojure 代码也会如此。Clojure 会采用一些未文档化的其他字符作为其内部符号,另外将来也可能会为符号增加更多的合法字符。
/和.会被Clojure加以特殊对待,用于支持命名空间
字符串与字符字符串是另外一种读取器形式。Clojure字符串就是Java字符串。它们使用双引号来划定界限,并且可以跨越多行。
REPL 回显字符串的字面量时,总是包括了换行转义符。如果确实“打印”了一个多行字符串,那它就会以多行的方式输出。
Clojure并未封装大多数的Java字符串功能。作为替代,可以使用Clojure的Java互操作形式来直接调用它们。
被Clojure封装了的字符串功能之一是toString。无需直接调用toString,而是应该使用Clojure的str函数。
str函数与toString有两点不同。一是它能接受多个参数,二是它会跳过nil而不引发错误。
Clojure字符同样也是Java字符。其字面语法是\{letter},letter可以是一个字母,或者下列这些字符的名称:backspace、formfeed、newline、return、space和tab。
和字符串一样,Clojure 并未封装 Java 的字符处理功能。因此,同样可以使用Java互操作,比如Character/toUpperCase。
字符串是由字符组成的序列。当对字符串调用Clojure的序列处理函数时,会得到由这些函数返回的一个字符序列。
直接使用str函数把这些字符打包进一个字符串的想法极具诱惑力,但很可惜这是行不通的。
最主要的问题是,str函数接受的是数量可变的参数,但传给它的参数只有一个,一个包含了参数列表的序列。解决方案是apply函数。
apply函数接受一个函数f、一些可选的args和一个序列argseq作为参数。然后,他会调用f,并将args和argseq解开为一个参数列表传给f。
调用(take-nth 2 ...),会从序列中依次剔除每第2个元素,这样就还原了被混淆的消息。
布尔值与nil● true为真,false为假。
● 除了false,在进行布尔求值的上下文中,nil也为假。
● 除了false和nil,其他任何东西在布尔求值的上下文中都为真。
Lisp程序员们请注意:在Clojure中,空列表不为假。
在Clojure中,零也不为假。
谓词(predicate)返回true或false的函数。
作为Clojure惯例,给谓词命名时皆以问号结尾,例如true?、false?、nil?和zero?。
true?用于测试值是否确实为 true,而不仅仅是在布尔求值的上下文中为真。唯一能通过true?测试的只有true本身。
nil?和false?也是一样。只有nil能通过nil?的测试,也只有false能通过false?的测试。zero?可以对任何一种数值类型使用,如果值为零则返回true。
可在REPL中输入(find-doc #"\?$")来查看谓词。
映射表、关键字和记录Clojure中,映射表是一种由键值对组成的容器,使用一对花括号作为其字面表示法。
值“McCarthy”被关联至键“Lisp”,同时值“Hickey”被关联至键“Clojure”。
可以使用逗号来分割每组键值对。Clojure对此毫不在意,它会把逗号视为空格符。
映射表同时也是函数。如果把键作为参数传给映射表,那么它会返回与这个键对应的值,或是当键不存在时直接返回nil。
由于Clojure 的数据结构是不可变的,并且都正确的实现了hashCode,所以任意一种Clojure数据结构都可以用做映射表的键。其中,Clojure关键字是一种相当常见的键类型。除了是以冒号(:)起头之外,关键字的其他部分与符号非常相似。
关键字会解析为它们自身,与符号之间最大的不同,符号总是会引用某种东西。
关键字会被解析为他们自身的这个特点,非常有利于令其成为映射表的键。可以使用关键字作为键。
关键字同样也是函数。它们接受一个映射表作为参数,并在该映射表中查找其自身。只要把关键字和inventors的位置相互调换,就可以通过调用映射表函数或关键字函数来查找语言的发明人了。
如果有好几个映射表都拥有相同的键,那么可以用 defrecord 创建一种记录类型,对这一事实加以描述(同时也是强化)。
此处的参数名称会转换为键。这样当创建一条记录时,就需要传入相对应的值。
使用defrecord创建了一种记录类型Book。
用user.Book来实例化一条记录了:
一旦实例化了一个Book,会发现它的行为与其他映射表简直像极了。
记录也可以使用别的方法来实例化。
也可以采用字面语法来实例化一条记录。要这么做,只要照原样输入在REPL中看到的返回内容即可。会发现与前面唯一的区别是,记录的字面量必须使用全限定名。