Clojure 编程语言:充分利用 Eclipse 的 Clojure 插件(3)

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

清单 5. 针对 Fibonacci 数的一个惰性序列

(defn lazy-seq-fibo ([] (concat [0 1] (lazy-seq-fibo 0 1))) ([a b] (let [n (+ a b)] (lazy-seq (cons n (lazy-seq-fibo b n))))))

在清单 5 中,lazy-seq-fibo 函数具有两个定义。第一个定义没有参数,因此方括号是空的。第二个定义有两个参数 [a b]。对于没有参数的情况,我们获取序列 [0 1] 并将它连到一个表达式。该表达式是对 lazy-seq-fibo 的一个递归调用,不过这次,它调用的是有两个参数的情况,并向其传递 0 和 1。

两个参数的情况从 let 表达式开始。这是 Clojure 内的变量赋值。表达式 [n (+ a b)] 设置变量 n 并将其设为等于 a+b。然后它再使用 lazy-seq 宏。正如其名字所暗示的,lazy-seq 宏被用来创建一个惰性序列。其主体是一个表达式。在本例中,它使用了 cons 函数。该函数是 Lisp 内的一个典型函数。它接受一个元素和一个序列并通过将元素添加在序列之前来返回一个新序列。在本例中,此序列就是调用 lazy-seq-fibo 函数后的结果。如果这个序列不是惰性的,lazy-seq-fibo 函数就会一次又一次地被调用。不过,lazy-seq 宏确保了此函数将只在元素被访问的时候调用。为了查看此序列的实际处理,可以使用 REPL,如清单 6 所示。

清单 6. 生成 Fibonacci 数

1:1 user=> (defn lazy-seq-fibo ([] (concat [0 1] (lazy-seq-fibo 0 1))) ([a b] (let [n (+ a b)] (lazy-seq (cons n (lazy-seq-fibo b n)))))) #'user/lazy-seq-fibo 1:8 user=> (take 10 (lazy-seq-fibo)) (0 1 1 2 3 5 8 13 21 34)

take 函数用来从一个序列中取得一定数量(在本例中是 10)的元素。我们已经具备了一种很好的生成 Fibonacci 数的方式,让我们来解决这个问题。

清单 7. 示例 2

(defn less-than-four-million? [n] (< n 4000000)) (println (reduce + (filter even? (take-while less-than-four-million? (lazy-seq-fibo)))))

在清单 7 中,我们定义了一个函数,称为 less-than-four-million?。它测试的是其输入是否小于 400 万。在接下来的表达式中,从最里面的表达式开始会很有用。我们首先获得一个无穷的 Fibonacci 序列。然后使用 take-while 函数。它类似于 take 函数,但它接受一个断言。一旦断言返回 false,它就停止从这个序列中获取。所以在本例中,Fibonacci 数一旦大于 400 万,我们就停止获取。我们取得这个结果并应用一个过滤器。此过滤器使用内置的 even? 函数。该函数的功能正如您所想:它测试一个数是否是偶数。结果得到的是所有小于 400 万且为偶数的 Fibonacci 数。现在我们对它们进行求和,使用 reduce,正如我们在第一个例子中所做的。

清单 7 虽然能解决这个问题,但是并不完全令人满意。要使用 take-while 函数,我们必须要定义一个十分简单的函数,称为 less-than-four-million?。而结果表明,这并非必需。Clojure 具备对闭包的支持,这没什么稀奇。这能简化代码,如清单 8 中所示。

Clojure 中的闭包

闭包在很多编程语言中非常常见,特别是在 Clojure 等函数语言中。这不仅仅是因为函数 “级别高” 且可被作为参数传递给其他函数,还因为它们可被内联定义或匿名定义。清单 8 是清单 7 的一个简化版,其中使用了闭包。

清单 8. 简化的解决方案

(println (reduce + (filter even? (take-while (fn [n] (< n 4000000)) (lazy-seq-fibo)))))

在清单 8 中,我们使用了 fn 宏。这会创建一个匿名函数并返回此函数。对函数使用断言通常很简单,而且最好使用闭包定义。而 Clojure 具有一种更为简化的方式来定义闭包。

清单 9. 简短的闭包

(println (reduce + (filter even? (take-while #(< % 4000000) (lazy-seq-fibo)))))

我们曾使用 # 创建闭包,而不是借助 fn 宏。而且我们还为传递给此函数的第一个参数使用了 % 符号。您也可以为第一个参数使用 %1,如果此函数接受多个参数,还可以使用类似的 %2、%3 等。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/343500d1f46862334c243a65e4062571.html