Programming Clojure笔记之四——函数式编程

函数式编程的一些概念

pure functions

pure function就是没有side effects的函数。除了参数不依赖其他,除了返回值不影响其他。

Persistent Data Structures

数据结构不可变是Clojure实现函数式编程和并发的关键。
然而数据不可变意味着更改时需要复制,为了保证性能,Clojure会共享数据

Laziness and Recursion

在Clojure中,函数和表达式不是惰性的,而序列一般是惰性的。

Referential Transparency

如何变得惰性

简单的递归

;这样递归会发生栈溢出
(defn stack-consuming-fibo [n] (cond (= n 0) 0 (= n 1) 1 :else (+ (stack-consuming-fibo (- n 1)) (stack-consuming-fibo (- n 2)))))

尾递归(tail recursion)

尾递归是递归发生在函数返回的表达式中。上面的函数之所以不是尾递归,因为递归之后还有一个加法操作。对于尾递归,Clojure可以进行tail-call optimization (TCO),即所谓的尾递归优化,将递归转化为迭代。

(defn tail-fibo [n] (letfn [(fib [current next n] (if (zero? n) current (fib next (+ current next) (dec n))))] (fib 0N 1N n)))
;使用letfn宏绑定局部函数,这样的函数能调用自身或者调用在同一个letfn块中的函数。
;即使使用了尾递归,由于JVM并不能执行TCO,依然会发生栈溢出。

使用recur进行self-recursion

;使用recur可以得到优化
(defn tail-fibo [n] (letfn [(fib [current next n] (if (zero? n) current (recur next (+ current next) (dec n))))] (fib 0N 1N n)))

惰性序列

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

;这里的关键是lazy-seq宏,使得lazy-seq-fibo的递归调用没有立即发生,不然就会发生栈溢出。

;大部分情况下不需要直接使用lazy-seq,而应该使用序列库函数。
;如下,使用interate函数生成斐波拉契序列。
(take 5 (iterate (fn [[a b]] [b (+ a b)]) [0 1]))
-> ([0 1] [1 1] [1 2] [2 3] [3 5])

(defn fibo []
  (map first (iterate (fn [[a b]] [b (+ a b)]) [0N 1N])))

不要引用头元素

;不要这样直接定义无限序列
(def head-fibo (lazy-cat [0N 1N] (map + head-fibo (rest head-fibo))))

Lazier Than Lazy(尽量使用序列库)

;假如有个:h:t组成的序列,需要计算序列中的连续:h对。使用loop和recur这么做:
(defn count-heads-pairs [coll]
  (loop [cnt 0 coll coll]
    (if (empty? coll)
      cnt
      (recur (if (= :h (first coll) (second coll))
              (inc cnt)
              cnt)
             (rest coll)))))
;一般应该将问题分解然后使用库函数解决
;首先将序列分成一对对
(defn by-pairs [coll]
  (let [take-pair (fn [c]
                      (when (next c) (take 2 c)))]
       (lazy-seq
         (when-let [pair (seq (take-pair coll))]
           (cons pair (by-pairs (rest coll)))))))
;然后计算都等于:h的序列对个数即可
(defn count-heads-pairs [coll]
  (count (filter (fn [pair] (every? #(= :h %) pair))
                 (by-pairs coll))))

;其实序列库中有个函数partition可以用来替代by-pairs
;(partition size step? coll)
(partition 2 1 [:h :t :t :h :h :h])
-> ((:h :t) (:t :t) (:t :h) (:h :h) (:h :h))

;事实上count/filter很常用,因此有必要将两个函数结合成一个新函数。comp函数(compose)就是用来组合函数的。
(def ^{:doc "Count items matching a filter"}
    count-if (comp count filter))

;所以可以定义一个比cout-heads-pairs更加通用的函数
(defn count-runs
  "Count runs of length n where pred is true in coll."
  [n pred coll]
  (count-if #(every? pred %) (partition n 1 coll)))
;调用
(count-runs 2 #(= % :h) [:h :t :t :h :h :h])
-> 2

partial application

;如果依然需要一个count-heads-pairs函数,可以使用partial根据count-runs函数定义
(def ^{:doc "Count runs of length two that are both heads"} count-heads-pairs (partial count-runs 2 #(= % :h)))

;partial是对一个函数的部分应用

你可能感兴趣的:(函数式编程,clojure)