Clojure 编程语言

来源:developworks 作者:Michael Galpin
  
Lisp 是一种编程语言,以表达性和功能强大著称,但人们通常认为它不太适合应用于一般情况。Clojure 是一种运行在 Java™ 平台上的 Lisp 方言,它的出现彻底改变了这一现状。如今,在任何具备 Java 虚拟机的地方,您都可以利用 Lisp 的强大功能。在本文中,了解如何开始使用 Clojure,学习它的一些语法,同时利用 Eclipse 的 Clojure 插件提供帮助。

本文介绍了 Clojure 编程语言。Clojure 是一种 Lisp 方言。本文假设您不熟悉 Lisp,但需要您具备 Java 技术方面的知识。要编写 Clojure 程序,需要 Java Development Kit V5 或更高版本以及 Clojure 库。本文使用的是 JDK V1.6.0_13 和 Clojure V1。此外,您还需要利用 Eclipse 的 Clojure 插件(clojure-dev),因此还要用到 Eclipse。在本文中,我们使用了 Eclipse V3.5 和 clojure-dev 0.0.34。相关链接,请参见 参考资料

什么是 Clojure?

不久前,要想在 Java Virtual Machine (JVM) 上运行程序,还需要使用 Java 编程语言来编写程序。但那个时代已经一去不复返了。现在更多的选择,比如 Groovy、Ruby(通过 JRuby)及 Python (通过 Jython),带来了一种更为过程化的脚本式的编程风格,或者它们各自拥有独特的面向对象编程特色。这两种都是 Java 程序员所熟悉的。也许有人会说用这些语言与用 Java 语言编写程序没什么区别,只要习惯不同的语法就可以了。

虽然 Clojure 还不算是 JVM 的一种新的编程语言,但它与 Java 技术及前面提到过的其他任何 JVM 语言都有很大的区别。它是一种 Lisp 方言。从 20 世纪 50 年代开始至今,Lisp 语言家族已经存在很长时间了。Lisp 使用的是截然不同的 S-表达式或前缀 注释。这个注释可以被归结为 (function arguments...)。通常总是从函数名开始,然后列出要向这个函数添加的零个或多个参数。函数及其参数通过圆括号组织在一起。数量众多的括号也成为了 Lisp 的一大特征。

您可能已经发现,Clojure 是一种函数式编程语言。专业人士可能会说它太过单一,但实际上它却囊括了函数式编程的所有精华:避免了不稳定状态、递归、更高阶的函数等。Clojure 还是一个动态类型的语言,您可以选择添加类型信息来提高代码中的关键路径的性能。Clojure 不仅可在 JVM 上运行,而且在设计上还兼顾了 Java 的互操作性。最后,Clojure 在设计上也考虑了并发性,并具有并发编程的一些独特特性。




Clojure 示例

对大多数人来说,学习一种新的编程语言的最佳方法是从练习编写代码开始。按照这个思路,我们将提出一些简单的编程问题,然后用 Clojure 来解决这些问题。我们将深入剖析每种解决方案以便您能更好地理解 Clojure 是如何工作的、该如何使用它、它最擅长什么。不过,像其他语言一样,要想使用 Clojure,我们需要先为它建立一个开发环境。幸好,建立 Clojure 环境非常容易。

最低设置

要建立 Clojure 语言环境,您所需要的就是一个 JDK 和 Clojure 库(一个 JAR 文件)。开发和运行 Clojure 程序有两种常用方式。其中最常用的一种方法是使用它的 REPL(read-eval-print-loop)。


清单 1. Clojure REPL
				
$ java -cp clojure-1.0.0.jar clojure.lang.Repl
Clojure 1.0.0-
user=> 
            

此命令从 Clojure JAR 所在的目录运行。按需要,将路径调整到 JAR。您还可以创建一个脚本并执行此脚本。为此,需要执行一个名为 clojure.main 的 Java 类。


清单 2. Clojure main
				
$ java -cp clojure-1.0.0.jar clojure.main /some/path/to/Euler1.clj 
233168
            

同样,您需要将路径调整到 Clojure JAR 及脚本。Clojure 终于有了 IDE 支持。Eclipse 用户可以通过 Eclipse 升级网站来安装 clojure-dev 插件。安装完毕且确保处于 Java 透视图中后,就可以创建一个新的 Clojure 项目和新的 Clojure 文件了,如下所示。


图 1. 使用 Eclipse 的 Clojure 插件 clojure-dev
使用 Eclipse 的 Clojure 插件 clojure-dev

有了 clojure-dev,您就能够获得一些基本语法的亮点,包括圆括号匹配(Lisp 所必需的)。您还可以在被直接嵌入到 Eclipse 的一个 REPL 中放入任意脚本。这个插件还很新,在本文写作之时,它的特性还在不断发展。现在,我们已经解决了基础设置的问题,接下来让我们通过编写一些 Clojure 程序来进一步研究这个编程语言。




示例 1:处理序列

Lisp 这一名字来自于 “列表处理”,人们常说 Lisp 中的任何东西都是一个列表。在 Clojure 中,列表被统一成了序列。在第一个示例中,我们将处理下述的编程问题。

如果我们要列出 10 以下且为 3 或 5 的倍数的所有自然数,我们将得到 3、5、6 和 9。这几个数的和是 23。我们的题目是求出 1,000 以下且为 3 或 5 的倍数的自然数的和。

这个题目取自 Project Euler,Project Euler 是一些可以通过巧妙(有时也不是很巧妙)的计算机编程解决的数学题集。实际上,这就是我们的问题 1。清单 3 给出了这个问题的解决方案,其中使用了 Clojure。


清单 3. Project Euler 中的示例 1
				
(defn divisible-by-3-or-5? [num] (or (== (mod num 3) 0)(== (mod num 5) 0))) 

(println (reduce + (filter divisible-by-3-or-5? (range 1000))))
            

第一行定义了一个函数。记住:函数是 Clojure 程序的基石。大多数 Java 编程员都习惯于把对象作为其程序的基石,所以一些人可能需要一些时间才能习惯使用函数。您可能会认为 defn 是此语言的关键字,但它实际上是个宏。一个宏允许您对 Clojure 做扩展以向该语言中添加新的关键字。也就是说,defn 并不是此语言规范的一部分,而是通过此语言的核心库被添加上的。

在本例中,第一行实际上是创建了一个名为 divisible-by-3-or-5? 的函数。这遵循了 Clojure 的命名约定。单词均以连字符分隔,并且此函数的名字是以一个问号结尾的,用以表示此函数是一个断言,因它会返回 true 或 false。此函数只接受一个名为 num 的单一参数。如果有更多的输入参数,它们将显示在这个方括号内,以空格分隔。

下面是这个函数的主体。首先,我们调用 or 函数。这是常用的 or 逻辑;它是一个函数,而不是一个操作符。我们将它传递给参数。而每个参数也是一个表达式。第一个表达式是以 == 函数开始的。它对传递进来的这些参数的值进行比较。传递给它的有两个参数。第一个参数是另一个表达式;这个表达式调用 mod 函数。这是数学里的模运算符或 Java 语言里的 % 运算符。它返回的是余数,所以在本示例中,余数是 num 被 3 除后的余数。该余数与 0 比较(如果余数是 0,那么 num 可以被 3 整除)。同样地,我们检查 num 被 5 除后的余数是否是 0。如果这两种情况的余数有一个是 0,那么此函数返回 true。

在接下来的一行,我们创建一个表达式并把它打印出来。让我们从圆括号的最里面开始。在这里,我们调用了 range 函数并将数 1,000 传递给它。这会创建一个由 0 开始,所有小于 1,000 的数组成的序列。这组数正是我们想要检查是否可被 3 或 5 整除的那些数。向外移,我们会调用 filter 函数。此函数接受两个参数:第一个是另一个函数,该函数必须是一个断言,因它必须要返回 true 或 false;第二个参数是一个序列 — 在本例中,此序列是 (0, 1, 2, ... 999)。filter 函数被应用到这个断言,如果该断言返回 true,序列中的元素就被加到此结果。这个断言就是在上一行中定义的 divisible-by-3-or-5? 函数。

因此,这个过滤器表达式的结果是一个整数序列,其中每个整数都小于 1,000 且能被 3 或 5 整除。而这也正好是我们感兴趣的那组整数,现在,我们只需将它们相加。为此,我们使用 reduce 函数。这个函数接受两个参数:一个函数和一个序列。它将此函数应用到序列中的前两个元素。然后再将此函数应用到之前的结果以及序列中的下一个元素。在本例中,该函数就是 + 函数,即加函数。它能将该序列中的所有元素都加起来。

从清单 3 中,不难看出在这一小段代码中发生了很多事情。而这也恰好是 Clojure 吸引人之处。发生的操作虽然很多,但如果熟悉了这些注释,代码将很容易读懂。同样的事情,若用 Java 代码去做,则需要用到更多的代码量。让我们接下来看看另一个例子。




示例 2:惰性成了长处

通过这个例子,我们来探讨一下 Clojure 内的递归和惰性。这对于很多 Java 程序员而言是另一个新概念。Clojure 允许定义 “懒惰” 的序列,即其中的元素只有在需要的时候才进行计算。借此,您就可以定义无穷序列,这在 Java 语言中是从未有过的。要了解这一点是多么地有用,可以看看下面这个例子,该例涉及到了函数式语言的另一个十分重要的方面:递归。同样地,我们仍然使用 Project Euler 中的一个编程问题,但是这次,它是问题 2。

Fibonacci 序列中的每个新的项都是其前面两项相加的结果。从 1 和 2 开始,前 10 项将是:1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...

现在,我们要找到此序列中所有偶数项之和,且不超过 400 万。为了解决这个问题,Java 程序员一般会想到要定义一个函数来给出第 n 个 Fibonacci 数。这个问题的一个简单实现如下所示。


清单 4. 一个简单的 Fibonacci 函数
				
(defn fib [n] 
    (if (= n 0) 0
        (if (= n 1) 1
            (+ (fib (- n 1)) (fib (- n 2))))))        
            

它检查 n 是否为 0;如果是,就返回 0。然后检查 n 是否为 1。如果是,就返回 1。否则,计算第 (n-1) 个 Fibonacci 数和第 (n-2) 个 Fibonacci 数并将二者加起来。这当然很正确,但是如果您已经进行了很多的 Java 编程,就会看到问题。像这样的递归定义很快就会填满堆栈,从而导致堆栈溢出。Fibonacci 数形成了一个无穷序列,所以应该用 Clojure 的无穷惰性序列描述它,如清单 5 所示。请注意虽然 Clojure 具有一个更为有效的 Fibonacci 实现,是标准库(clojure-contrib)的一部分,但它较为复杂,因此这里所示的这个 Fibonacci 序列来自于 Stuart Halloway 的一本书(更多信息,请参见


时间:2009-11-06 21:00 来源:developworks 作者:Michael Galpin 原文链接

好文,顶一下
(6)
75%
文章真差,踩一下
(2)
25%
------分隔线----------------------------


把开源带在你的身边-精美linux小纪念品
无觅相关文章插件,快速提升流量