如果我们要列出 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))))))