Clojure快餐教程(1) - 运行在JVM上的Lisp方言

简介: Clojure是一门运行在JVM上的Lisp方言.与Lisp还是有一些不同,比如使用`[]`,`{}`,`#{}`来表示向量、哈希表和集合。比如不支持car, cdr, setq等。好处是可以无缝调用java的基础设施,同时做了一些修改,更加适合多人协作开发。

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表达式可以通俗理解为一个括号括起来的,以第一个符号进行求值的表达式。
所以,如果要避免一个列表被求值,我们有两种办法:

  1. 用list函数来生成列表
  2. 用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宏可以定义函数的说明文档
目录
相关文章
|
2天前
|
存储 缓存 算法
深入浅出JVM(二)之运行时数据区和内存溢出异常
深入浅出JVM(二)之运行时数据区和内存溢出异常
|
3天前
|
存储 缓存 Java
JVM 运行时内存篇
JVM 运行时内存篇
7 0
|
4天前
|
存储 监控 安全
JVM工作原理与实战(十九):运行时数据区-方法区
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了方法区、方法区在Java虚拟机的实现、类的元信息、运行时常量池、字符串常量池、静态变量的存储等内容。
10 0
|
4天前
|
存储 Arthas 监控
JVM工作原理与实战(十八):运行时数据区-堆
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了运行时数据区、堆介绍、堆的关键参数等内容。
|
4天前
|
存储 监控 Java
JVM工作原理与实战(十七):运行时数据区-栈内存溢出
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了栈内存溢出、设置虚拟机栈的大小等内容。
11 0
|
4天前
|
存储 监控 安全
JVM工作原理与实战(十六):运行时数据区-Java虚拟机栈
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了运行时数据区、Java虚拟机栈等内容。
11 0
|
4天前
|
存储 监控 安全
JVM工作原理与实战(十五):运行时数据区-程序计数器
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了运行时数据区、程序计数器等内容。
10 0
|
12天前
|
小程序 Java 程序员
【Java探索之旅】我与Java的初相识(二):程序结构与运行关系和JDK,JRE,JVM的关系
【Java探索之旅】我与Java的初相识(二):程序结构与运行关系和JDK,JRE,JVM的关系
28 0
|
2月前
|
缓存 监控 Java
深入剖析JVM的OOM | 内存溢出如何影响JVM运行及应对策略
深入剖析JVM的OOM | 内存溢出如何影响JVM运行及应对策略
105933 1
|
4月前
|
存储 Java C++
JVM 运行时数据区
JVM 运行时数据区