(defn double-+
[a b]
(* 2 (+ a b)))
在函式程式設計中,函式是一級公民。這表示函式可以視為值。它們可以指派為值、傳遞到函式中,以及從函式中傳回。
在 Clojure 中,常見到使用 defn
定義函數,例如 (defn foo …)
。不過,這只是 (def foo (fn …))
的語法糖衣,fn
會傳回函數物件,而 defn
會傳回指向函數物件的變數。
高階函數是一種函數,它
將一個或多個函數作為參數
傳回一個函數作為結果
這是任何語言中函數式程式設計的重要概念。
高階函數讓我們可以組合函數。這表示我們可以撰寫小型函數,並將它們組合起來建立較大的函數。就像將許多小型的樂高積木組合起來蓋房子一樣。
讓我們暫時離開理論,來看一個範例。
匿名函數是不帶名稱的函數。在 Clojure 中,可以使用兩種方式定義它們:fn
和文字 #(…)
。使用 defn
建立函數會立即將它繫結到名稱,而 fn
只會建立一個函數。
讓我們來看一個有關幾個樂團的範例
(def bands [
{:name "Brown Beaters" :genre :rock}
{:name "Sunday Sunshine" :genre :blues}
{:name "Foolish Beaters" :genre :rock}
{:name "Monday Blues" :genre :blues}
{:name "Friday Fewer" :genre :blues}
{:name "Saturday Stars" :genre :jazz}
{:name "Sunday Brunch" :genre :jazz}
])
我們只想擷取搖滾樂團。這是一個一次性的運算,我們不會在程式碼中的其他地方使用它。我們可以使用匿名函數來節省一些按鍵。
(def rock-bands
(filter
(fn [band] (= :rock (:genre band)))
bands))
使用函數文字,我們可以更簡潔地定義 rock-bands
,如下所示。
(def rock-bands (filter #(= :rock (:genre %)) bands))
函數文字支援透過 %
、%n
和 %&
傳入多個參數。
#(println %1 %2 %3)
如果你正在撰寫匿名函數,文字語法很好用,因為它很精簡。不過,如果參數超過幾個,語法可能會難以閱讀。在這種情況下,使用 fn
可能會更合適。
我們的第一個函數將稱為 adder
。它將採用一個數字 x
作為其唯一參數並傳回一個函數。adder
傳回的函數也將採用一個數字 a
作為其參數並傳回 x + a
。
(defn adder [x]
(fn [a] (+ x a)))
(def add-five (adder 5))
(add-five 100)
;; => 105
從 adder
傳回的函數是一個閉包。這表示它可以存取函數建立時作用域內的所有變數。add-five
可以存取 x
,即使它在 adder
函數定義之外。
篩選是電腦程式設計中常見的操作。採用這組動物
(def pets [
{:name "Fluffykins" :type :cat}
{:name "Sparky" :type :dog}
{:name "Tibby" :type :dog}
{:name "Al" :type :fish}
{:name "Victor" :type :bear}
])
我們想要篩選出非狗的動物,因為我們正在撰寫企業級軟體。首先,讓我們來看看一個正常的 for 迴圈。
(defn loop-dogs [pets]
(loop [pets pets
dogs []]
(if (first pets)
(recur (rest pets)
(if (= :dog (:type (first pets)))
(conj dogs (first pets))
dogs))
dogs)))
這段程式碼運作良好,但它龐大且令人困惑。我們可以使用高階函數 filter
來簡化它。
(defn filter-dogs [pets]
(filter #(= :dog (:type %)) pets))
使用 filter
的解決方案更清楚,並允許我們顯示意圖,而不仅仅是下達指令。我們可以將其分解成更小的部分,方法是將篩選函數分解成一個單獨的 var
。
(defn dog? [pet] (= :dog (:type pet)))
(defn filter-dogs [pets] (filter dog? pets))
原始作者:Michael Zavarella