01.环境搭建

Clojure是什么?

Clojure是一门运行在jvm环境下的**Lisp方言。**笔者一直久仰Lisp的大名,最近终于克服了对层层括号的恐惧,准备一睹Lisp的芳容啦!实际上手体验之后感觉Lisp系语言写起来还是蛮爽的,最重要的是REPL实在是太方便了,有了想法之后就可以立刻付诸实践,不用再专门写一个main函数或是单元测试来进行验证,直接用REPL交互式的进行实验即可。

img

运行环境搭建

由于Clojure是运行在jvm上的语言,自然需要先配置好java运行环境。之后我们需要安装一个Clojure依赖管理工具Leiningen(以下简称lein,安装完毕后别忘了将bin目录添加到PATH环境变量中。

现在让我们打开cmd,执行下面这条命令来查看lein是否安装正确:

lein version

如果cmd中显示这么一行信息,就表示lein已经正确安装了。

Leiningen 2.8.1 on Java 1.8.0_144 Java HotSpot(TM) 64-Bit Server VM

使用REPL

所谓的REPL就是读取-求值-打印-循环,当我们在REPL中输入一条表达式之后,表达式的结果会立即回显到cmd中,然后等待下一条输入。类似Python等很多脚本语言都带有REPLClojure自然也不例外。

cmd中执行下面这条命令就能打开ClojureREPL了:

lein repl

之后会在cmd中输出下列信息:

nREPL server started on port 49928 on host 127.0.0.1 - nrepl://127.0.0.1:49928
REPL-y 0.3.7, nREPL 0.2.12
Clojure 1.8.0
Java HotSpot(TM) 64-Bit Server VM 1.8.0_144-b01
 Docs: (doc function-name-here)
 (find-doc "part-of-name-here")
 Source: (source function-name-here)
 Javadoc: (javadoc java-object-or-class-here)
 Exit: Control+D or (exit) or (quit)
 Results: Stored in vars *1, *2, *3, an exception in *e

user=>

我们试着在REPL中运行一个Hello World程序:

user=> (println "Hello World!")
;; Hello World!
;=> nil

user=>表示我们目前处于user命名空间当中。user命名空间是REPL环境的默认命名空间。之后如无必要将会在例程中省略user=>提示符。

Lisp系语言以分号(;)表示注释,相当于C语言中的(//。我会用双分号注释(;;)表示命令行输出,箭头注释(;=>)表示表达式的值。

现在让我们把重点放到程序本身:

(println Hello World!)

Lisp中,一对括号表示一个列表结构,其中列表项以空格进行分隔(注:在Clojure中,逗号(,)和空格是等价的,因此也可以像其他语言一样用逗号进行分隔。不同于其他语言的数组,Lisp列表再被求值时,会被解释成一个函数调用。列表的第一项会被解释成函数名,其余则是函数的参数。

至于Lisp为何要这么做?那可就说来话长了,现阶段我们只需要简单的记下即可。

让我们再看一个简单的例子,使用Clojure计算1+1

(+ 1 1)
;=> 2

没错,在Clojure(+)也是一个函数,因此它看起来是前缀形式的。这看起来比较别扭,不过好处是我们不再需要考虑运算符优先级的问题了:

(* 3 (+ 1 2))
;=> 9

另一个优势是,前缀形式的(+)函数可以很自然的接受任意数量的参数:

(+ 1 2 3 4 5)
;=> 15

因此(+)函数很自然的能起到列表求和的作用:

(apply + [1 2 3 4 5])
;=> 15

apply函数的作用是将一个列表中的元素作为函数参数传递给一个函数。

文档查询

REPL还提供了非常方便的文档查询功能。

  • 使用doc宏查询文档

我们可以用doc来查看一下+函数的文档:

(doc +)
;;-------------------------
;;clojure.core/+
;;([] [x] [x y] [x y & more])
;;  Returns the sum of nums. (+) returns 0. Does not auto-promote
;;  longs, will throw on overflow. See also: +'
;=> nil

在阅读Clojure代码时,如果遇到没见过的函数或宏,马上打开REPL查询一下吧!

  • 使用find-doc寻找文档

我们试着来求一下10的异或:

(xor 1 0)
;=> CompilerException java.lang.RuntimeException: Unable to resolve symbol: xor in this context, compiling:(null:1:1)

哎呀!Clojure似乎没有一个叫xor的函数。不用担心,我们试试用find-doc来查找一下相关函数吧:

(find-doc "xor")
;;-------------------------
;;clojure.core/bit-xor
;;([x y] [x y & more])
;;  Bitwise exclusive or
;=> nil

原来函数名是bit-xor!让我们来尝试一下:

(bit-xor 0 1)
;=> 1
  • 使用javadoc查询java类库的文档:
(javadoc java.util.ArrayList)

REPL会为我们打开一个浏览器页面,显示ArrayList的文档。

使用Lein新建Clojure项目

使用Lein创建Clojure项目也非常简单,我们先在cmdcd到项目要保存的目录下,然后执行这个命令即可:

lein new learning-clojure

这样我们就创建了一个名为learning-clojure的项目:

img

项目的目录结构有点像maven,包含一个包含源文件的src文件夹,和一个包含测试文件的test文件夹。

使用REPL对项目进行测试

后缀名为.clj的文件就是Clojure的源文件了。让我们来看一下src/learning_clojure/core.clj里面都有些什么内容吧:

(ns learning-clojure.core)    ; 声明命名空间

(defn foo ; 定义函数foo
 "I don't do a whole lot."    ; 这是foo函数的说明文档
 [x]                          ; 这是foo函数的参数列表
 (println x "Hello, World!")) ; 这是foo函数的函数体

上面的代码定义了一个简单的函数foo。让我们试着使用REPL来测试一下这段代码,首先需要在cmdcd到项目的根目录下,然后运行lein repl启动REPL

接下来我们使用use指令将learning-clojure.core命名空间中的内容导入到当前命名空间:

(use learning-clojure.core) ; <-注意此处的单引号

为什么这里要加上一个单引号呢?这是因为当Lisp对列表进行求值时,会先对列表的每一项进行求值。然而,在我们引入命名空间之前,learning-clojure.core还不存在,对它求值会发生错误。而单引号(读作quote)的作用就是阻止对其进行求值:

(a b c) ; 标识符a、b、c还不存在,无法求值
;=> CompilerException java.lang.RuntimeException: Unable to resolve symbol: a in this context, compiling:(null:1:1)
(a b c) ; 加上单引号之后会返回列表本身
;=> (a b c)
(quote (a b c)) ; 单引号实际上是quote宏的简写形式
;=> (a b c)

引入命名空间之后我们就能使用REPLfoo函数进行测试了:

(foo "Bar")
;; Bar Hello, World!
;=> nil

现在让我们在core.clj文件中添加一个hello函数:

(defn hello
 "Say hello to someone."
 [name]
 (str "Hello, " name))

保存修改之后,我们需要在REPL中使用:reload命令重新加载模块

(use :reload learning-clojure.core)
(hello World!)
;=> Hello, World!
下一页