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月前
|
Arthas 监控 Java
JVM知识体系学习七:了解JVM常用命令行参数、GC日志详解、调优三大方面(JVM规划和预调优、优化JVM环境、JVM运行出现的各种问题)、Arthas
这篇文章全面介绍了JVM的命令行参数、GC日志分析以及性能调优的各个方面,包括监控工具使用和实际案例分析。
52 3
|
4月前
|
存储 安全 Java
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程,JDK、JRE、JVM关系;程序计数器,堆,虚拟机栈,堆栈的区别是什么,方法区,直接内存
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程是什么,JDK、JRE、JVM的联系与区别;什么是程序计数器,堆,虚拟机栈,栈内存溢出,堆栈的区别是什么,方法区,直接内存
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程,JDK、JRE、JVM关系;程序计数器,堆,虚拟机栈,堆栈的区别是什么,方法区,直接内存
|
5月前
|
Java 编译器 程序员
JVM常见面试题(一):JVM是什么、由哪些部分组成、运行流程是什么,JDK、JRE、JVM的联系与区别
JVM常见面试题(一):JVM是什么、由哪些部分组成、运行流程是什么,JDK、JRE、JVM的联系与区别
JVM常见面试题(一):JVM是什么、由哪些部分组成、运行流程是什么,JDK、JRE、JVM的联系与区别
|
4月前
|
消息中间件 设计模式 安全
多线程魔法:揭秘一个JVM中如何同时运行多个消费者
【8月更文挑战第22天】在Java虚拟机(JVM)中探索多消费者模式,此模式解耦生产与消费过程,提升系统性能。通过`ExecutorService`和`BlockingQueue`构建含2个生产者及4个消费者的系统,实现实时消息处理。多消费者模式虽增强处理能力,但也引入线程安全与资源竞争等挑战,需谨慎设计以确保高效稳定运行。
94 2
|
5月前
|
Java 程序员 C++
大牛程序员用Java手写JVM:刚好够运行 HelloWorld
大牛程序员用Java手写JVM:刚好够运行 HelloWorld
|
5月前
|
存储 算法 Java
(四)JVM成神路之深入理解虚拟机运行时数据区与内存溢出、内存泄露剖析
前面的文章中重点是对于JVM的子系统进行分析,在之前已经详细的阐述了虚拟机的类加载子系统以及执行引擎子系统,而本篇则准备对于JVM运行时的内存区域以及JVM运行时的内存溢出与内存泄露问题进行全面剖析。
100 0
|
5月前
|
JavaScript Java API
JAVA程序运行问题之JVM找到并开始执行main方法如何解决
JAVA程序运行问题之JVM找到并开始执行main方法如何解决
|
5月前
|
存储 Java
JAVA程序运行问题之JVM 中的栈如何解决
JAVA程序运行问题之JVM 中的栈如何解决
|
5月前
|
存储 算法 Java
JAVA程序运行问题之Java类加载到JVM中加载类时,实际上加载的是什么如何解决
JAVA程序运行问题之Java类加载到JVM中加载类时,实际上加载的是什么如何解决
|
6月前
|
算法 Java
Java垃圾回收(Garbage Collection,GC)是Java虚拟机(JVM)的一种自动内存管理机制,用于在运行时自动回收不再使用的对象所占的内存空间
【6月更文挑战第18天】Java的GC自动回收内存,包括标记清除(产生碎片)、复制(效率低)、标记整理(兼顾连续性与效率)和分代收集(区分新生代和老年代,用不同算法优化)等策略。现代JVM通常采用分代收集,以平衡性能和内存利用率。
72 3