Programming Clojure学习笔记——函数编程

来源:互联网 发布:tags是什么软件 编辑:程序博客网 时间:2024/05/16 19:31
5.2 如何延迟
函数编程大量使用递归定义,递归定义由两部分组成:
(1) 基础,明确列出序列的一些元素
(2) 归纳,提供规则利用序列元素,生成更多的元素

Clojure中有多种方法实现递归:
(1) 简单递归,使用函数调用自己实现归纳步
(2) 尾部递归,只在函数末尾调用函数自己实现归纳步
(3) 延迟序列,消除实际的递归,延后直到需要时再计算值
在Clojure中,采用延迟序列实现递归通常是最合适的方法。

示例:
简单递归
(defn stack-consuming-fibo [n]
    (cond
        (= n 0) 0
        (= n 1) 1
        :else (+ (stack-consuming-fibo (- n 1))
                    (stack-consuming-fibo (- n 2)))))
使用这种方法计算很大的Fibonacci数时堆栈异常

尾部递归
(defn tail-fibo [n]
     (letfn [(fib [current next n] (if (zero? n) current (fib next (+ current next) (dec n))))]
     (fib 0 1 n)))
其中letfn宏
(letfn fnspecs & body)
; fnspecs ==> (fname [params*] exprs)+
leftn像let但是用来定义局部函数
使用这种方法计算很大的Fibonacci数时堆栈异常

使用recur自递归
在Clojure中,可以将尾部调用自己的函数转换为使用recur的自递归函数。
上面的程序可以修改为:
(defn recur-fibo [n]
     (letfn [(fib [current next n] (if (zero? n) current (recur next (+ current next) (dec n))))]
     (fib 0 1 n)))
使用这种方法可以计算很大的Fibonacci数,但如果计算多个时,将多次计算非常浪费资源。

延迟序列
延迟序列通过lazy-seq宏构造
(lazy-seq & body)
延迟序列只在需要时才调用它的body,不管是序列是直接还是间接被调用,然后延迟序列将结果进行缓存为子序列调用做预备。

使用延迟序列上面的程序可以修改为:
(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))))))

通过iterate实现
(defn fibo []
     (map first (iterate (fn [[a b]] [b (+ a b)]) [0 1])))

实现(取延迟序列元素)
通过take获取前n个元素序列,如
(def lots-o-fibs (take 10000000 (bibo)))
通过nth获取序列第n个元素
(nth lots-o-fibs 100)

为了方便,可以设置参数*print-length*,指定打印原色个数
user=> (set! *print-length* 10)
10
设置后,只输出前10个元素,如下:
user=> (take 1000000000 (fibo))
(0 1 1 2 3 5 8 13 21 34 ...)

说明:
(1) 如果需要实现整个序列,可以使用doall或dorun。
(2) 将延迟序列作为函数形式暴露,返回一个需要的序列;而不要延迟序列作为一个变量来使用(这会导致延迟序列中已经访问过的变量不能被垃圾回收器自动收回,导致内存溢出)
原创粉丝点击