Clojure快餐教程(1) - 运行在JVM上的Lisp方言
Java作为目前为止被使用最广泛的使用虚拟机的编程语言,带动了JVM上语言族的繁荣。
有根红苗正的为JVM设计的动态语言Groovy,目前最主要被用于Gradle编译环境中;也有Jython, JRuby等动态语言在JVM上的实现,也有scala这样强大的混合语言。
在这之中,clojure是比较特殊的一种,它是Lisp语言在JVM上的一种方言。
使用clojure调用java
首先我们先看一下如何用clojure来调用java的方法。
有了这个利器之后,我们就获得了整个java世界的类库的强大支持。
首先我们看个例子,做个最简单的两个大整数的乘法。用Java写是这样风格的:
import java.math.BigInteger;
public class Test {
public static void test(){
BigInteger bi1 = new BigInteger("1234567890");
BigInteger bi2 = new BigInteger("9876543210");
System.out.println(bi1.multiply(bi2));
}
}
用Clojure写起来,一开始可能会有点不适应,会是这样的:
(ns .test2)
(import java.math.BigInteger)
(println (.multiply (new BigInteger "1234567890") (new BigInteger "9876543210")))
这里可能会给代码补全带来一点困难,因为在写.multiply
时,还不知道它是应用在哪个对象或者类上。但是Clojure就是这种风格了,函数名、运算符先行。
Clojure对于Lisp的改进
Clojure作为运行在JVM上的Lisp方言,第一个优势就是可以无缝调用Java API。另外,针对Lisp括号多的问题,Clojure除了S表达式中常用的()
之外,增加了[]
和{}
两种括号。
()
表示列表,[]
表示向量,{}
用于表示哈希表。
另外,#{}
用来表示集合。
user=> (class [])
clojure.lang.PersistentVector
user=> (class ())
clojure.lang.PersistentList$EmptyList
user=> (class {})
clojure.lang.PersistentArrayMap
user=> (class #{})
clojure.lang.PersistentHashSet
在Clojure中生存
查看帮助 - clojure.repl/doc
宏
doc宏用于查看常量、特殊表、函数或者宏的文档。在以后的编程中我们会经常使用它。
例:
user=> (doc doc)
-------------------------
clojure.repl/doc
([name])
Macro
Prints documentation for a var or special form given its name
列表
在Lisp中,列表数据和代码的表示形式一样,都是S表达式。S表达式可以通俗理解为一个括号括起来的,以第一个符号进行求值的表达式。
所以,如果要避免一个列表被求值,我们有两种办法:
- 用list函数来生成列表
- 用quota特殊表来避免被求值
例:
user=> (list 1 2 3)
(1 2 3)
user=> (doc list)
-------------------------
clojure.core/list
([& items])
Creates a new list containing the items.
nil
user=> (quote (2 3 4))
(2 3 4)
user=> (doc quote)
-------------------------
quote
(quote form)
Special Form
Yields the unevaluated form.
Please see http://clojure.org/special_forms#quote
nil
user=> '(3 4 5)
(3 4 5)
"'"符号是quote的简写形式。
列表操作
lisp中最常用的car和cdr在clojure中不被支持,当然更不用想caddr之类的了。
如果要取表头,需要使用first函数,例:
user=> (first (list 2 3 4))
2
相对地,取除了表头之外的部分,使用rest函数,例:
user=> (rest (list 2 3 4))
(3 4)
如果取最后一个元素,可以使用last函数获取最后一个元素,例:
user=> (last '(5 6 7))
7
一般地,我们如果想取第n个下标的元素的话,可以使用nth函数:
user=> (nth (list 8 9 10) 2)
10
虽然没有car和cdr,但是将fist和rest连接在一起的cons函数还是有的:
user=> (cons 1 '(2 3 4))
(1 2 3 4)
向量
向量有点类似于数组,对于随机访问元素进行优化。clojure使用中括号来表示向量。
由于不是用的S表达式,所以也就不用quote了。
first, last, nth和rest函数对于向量仍然适用。
user=> (first [1 2 3 4])
1
user=> (nth [4 5 6 7] 3)
7
user=> (rest [3 4 5 6])
(4 5 6)
但是请注意rest返回的并不是一个向量,而且一个序列类型。
我们可以用class函数来看一下具体的类型:
user=> (class (rest '(1 2 3)))
clojure.lang.PersistentList
user=> (class (rest [1 2 3]))
clojure.lang.PersistentVector$ChunkedSeq
哈希表
用完了小括号和中括号,下面是大括号出场的时候了。没错,大括号用来表示哈希表。例:
{:name "hello", :age 18}
访问使用键的名字为函数名来获取值,例:
user=> (:name {:name "Hello", :age 18})
"Hello"
集合
小中大括号都用完了肿么办,只好在大括号之前加个"#"来表示。例:
user=> #{ 1 2 3}
#{1 3 2}
集合中的元素不能重复。如果要构建一个set,可以使用sorted-set函数。
user=> (sorted-set 1 2 2 3)
#{1 2 3}
变量绑定
使用def特殊表可以将值绑定到一个全局变量上。
前面学习的数据结构,现在都可以用来绑定到变量上了。
def可以多次绑定,以最新绑定的值为准。例:
user=> (def a '(1 2 3))
#'user/a
定义函数
作为Lisp的一种方言,函数是一定在生存阶段要学懂的必备技能。
我们使用fn特殊表来定义函数,如果作为函数对象,或者叫做lambda表达式,可以不定义名字。
比如,我们定义一个求平方的匿名函数:
user=> (fn [i](* i i))
#object[user$eval17$fn__18 0x7f284218 "user$eval17$fn__18@7f284218"]
函数没有什么特殊的,也可以用def绑定到一个变量上,这就是我们通常定义函数的写法:
user=> (def sqr (fn [i](* i i)))
#'user/sqr
user=> (sqr 4)
16
我们也可以使用defn宏来定义函数:
user=> (defn sqr3 [i](* i i i))
#'user/sqr3
user=> (sqr3 4)
64
通过defn宏,还可以为函数提供说明文档,然后文档就可以通过doc函数来看了。例:
user=> (defn succ "return next x" [x](+ x 1))
#'user/succ
user=> (doc succ)
-------------------------
user/succ
([x])
return next x
nil
小结
- Clojure是一门运行在JVM上的Lisp方言。与Lisp还是有一些不同,比如使用
[]
,{}
,#{}
来表示向量、哈希表和集合。比如不支持car, cdr, setq等。 - Clojure支持通过import宏来引用java包
- Clojure通过new特殊表可以创建java对象
- clojure.core/doc函数用来查看帮助文档
- def特殊表用于绑定变量
- fn特殊表用于定义函数,主要用于定义匿名函数
- defn宏可以定义函数的说明文档