iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 7
1
Software Development

擁抱 Clojure系列 第 7

[第 07 天] 擁抱 Clojure:繫結與函式(一)

繫結與函式(一)

函式是函數式程式設計的核心,雖然各個流派談及函數式程式設計,都有自己的定見和看法,但是不變的核心仍然是函式,函式必須是程式語言的第一級公民 (First-class citizen)。

身爲程式語言第一級公民的函式,必須可以當成參數傳遞給其它函式、可以被作爲函式的返回值、可以把函式繫結給某個名稱、以及可以在動態執行期產生函式。

函式的事暫且按下不表,先讓我們了解 Clojure 中如何將資料給定個名字。

繫結

def

在之前的章節中我們不斷地在 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

let

除了使用 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 傾向提供簡潔方式解決問題。因此於 fndef 的基礎上,建立了 defn 用來建立函式:

(defn adder [x y] (+ x y))
;; => #'user/adder
(adder 2 6)
;; => 8

區域繫結

在函式本體之中,可以透過 let 建立只存在於函式之中的區域符號 (Symbol)。範例中區域符號 axbxy 的和、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

(未完待續)


上一篇
[第 06 天] 擁抱 Clojure:資料結構與型態(三)
下一篇
[第 08 天] 擁抱 Clojure:繫結與函式(二)
系列文
擁抱 Clojure30

1 則留言

1
taiansu
iT邦新手 5 級 ‧ 2018-01-01 16:00:05

喔喔!我喜歡 "繫結" 這個翻譯。

林萌 iT邦新手 5 級‧ 2018-01-01 17:45:46 檢舉

:)

我要留言

立即登入留言