函式是函數式程式設計的核心,雖然各個流派談及函數式程式設計,都有自己的定見和看法,但是不變的核心仍然是函式,函式必須是程式語言的第一級公民 (First-class citizen)。
身爲程式語言第一級公民的函式,必須可以當成參數傳遞給其它函式、可以被作爲函式的返回值、可以把函式繫結給某個名稱、以及可以在動態執行期產生函式。
函式的事暫且按下不表,先讓我們了解 Clojure 中如何將資料給定個名字。
在之前的章節中我們不斷地在 REPL 中輸入資料,如果想要引用資料必須重新輸入才行。如果有一種方法,可以將資料賦予名字,需要使用的時候只要利用名字就可以參考,免去一再重複輸入的不便。
當然有!
Clojure 提供了類似其它程式語言定義變數的功能,透過 def
創建一個符號 (Symbol) 連結到資料,之後只要使用這個符號,Clojure 就會尋找到對應的資料:
(def answer 42)
;; => #'user/answer
answer
;; => 42
透過使用 def
創建了符號之後,其實符號並不直接參考到實際資料,而是參考到 def
所創造的 Vars 物件,Vars 物件則存放了實際的資料。Vars 物件對應了其它程式語言變數的概念,但是不建議在多執行緒環境下使用。如果想要管理不同執行緒之間共同使用的狀態,可以參考後續狀態管理的章節。
Vars 物件被創建時,會賦予它預設的命名空間,之後的運算式可以透過符號取得對應的資料,它是全域的資料繫結 (Binding)。
answer
;; => 42
user/answer
;; => 42
想要單純使用 Vars 物件,而不是它儲存的資料,可以使用 var
:
(var answer)
;; => #'user/answer
Clojure 提供了簡易的寫法,在名稱前加上井號 (#) 以及單引號 ('):
#'answer
;; => #'user/answer
除了使用 def
建立全域的資料繫結 (Binding) 之外,Clojure 還提供 let
讓我們建立區域的資料繫結。使用 let
建立的繫結只在創建的區域內可見,出了區域之後就船過水無痕:
(def y 5)
;; => #'user/y
(let [x 1
y (+ x 2)]
y)
;; => 3
y
;; => 5
let
運算式接受一個向量,成對地擺放了名字與資料的對應,之後的運算式便可以使用剛剛設定好的繫結,一旦離開 let
運算式,原本在運算式中的繫結就消失不見了。
談完了全域與區域的繫結,現在回來談談函數式程式設計的核心:函式。以下將介紹如何在 Clojure 建立函式,以及函式的應用。
在 Clojure 中建立函式最簡單的方式是透過 fn
這個特殊形式 (Special form),建立的函式沒有名字,稱作匿名函式 (Anonymous function)。以下示範接受兩個參數,返回兩個參數相加的函式:
(fn [x y] (+ x y))
;; => #function[user/eval11037/fn--11038]
以上範例示範了接受 x 與 y 兩個參數的函式,將兩個參數相加之後返回。
fn
接受 let
風格的繫結方式,將參數寫在向量中指定名稱與順序,向量之後是函式的本體 (Body),函式的返回值爲函式本體最後一個運算式求得的值,不需明確指定返回值。
呼叫函式時,參數的擺放位置依據定義的順序依序擺放:
((fn [x y] (+ x y)) 3 5)
;; => 8
Clojure 提供了簡單的表示法可以快速地建立匿名函式 (Anonymous function),只要在括號前加上井號即可:
(#(str "Hello World"))
;; => "Hello World"
如果使用這種簡明表示法創建只接受一個參數的函式,可以在函式中使用百分比符號 (%) 表示參數:
(#(str "Hello " %) "Mike")
;; => "Hello Mike"
若是兩個參數以上,則在百分比符號之後分別加上 1, 2, 3 等數字,表明參數的個別順序:
(#(str "Hello " %1 ", " %2 ) "Mike" "Andy")
;; => "Hello Mike, Andy"
搭配 def
可以將一個匿名函式 (Anonymous function) 配上名字,之後只要以名稱便可以呼叫函式:
(def adder (fn [x y] (+ x y)))
;; => #'user/adder
(adder 3 5)
;; => 8
用 fn
搭配 def
雖然可以建立具有名稱的函式,但是太繁瑣了,Clojure 傾向提供簡潔方式解決問題。因此於 fn
與 def
的基礎上,建立了 defn
用來建立函式:
(defn adder [x y] (+ x y))
;; => #'user/adder
(adder 2 6)
;; => 8
在函式本體之中,可以透過 let
建立只存在於函式之中的區域符號 (Symbol)。範例中區域符號 a
是 x
、b
是 x
與 y
的和、c
則是 y
,返回值是三個區域符號的值加總:
(defn adder [x y]
(let [a x
b (+ x y)
c y]
(+ a b c)))
;; => #'user/adder
(adder 1 2)
;; => 6
defn
內部以 def
建立了 Vars 物件,指向以 fn
產生的函式,除了函式的參數與本體之外,你還可以加上函式的說明文字 (Docstring),向使用者說明使用方法或設計理念:
(defn adder
"Sum of two variables"
[x y]
(+ x y))
;; => #'user/adder
(doc adder)
;; => -------------------------
;; => user/adder
;; => ([x y])
;; => Sum of two variables
說明文字 (Docstring) 加在名稱之後、參數之前,可以寫上函式的說明、參數的意義或是使用函式需要注意的地方。函式中的說明文字,內部使用 def
將文字加入 Vars 物件的詮釋資料中 (Metadata):
(def a "Simple value" 5)
;; => #'user/a
(doc a)
;; => -------------------------
;; => user/a
;; => Simple value
(未完待續)