Java 中將類別裡擁有數個同樣名字的方法 (Method),參數個數卻不同稱爲多載 (Overloading),Clojure 也支援這種設計方法,同一個函式根據參數個數的不同,可以有不同的運算式,也可以呼叫同一個函式下不同參數個數的運算式:
(defn adder
([x] (adder x 10))
([x y] (+ x y)))
;; => #’user/adder
(adder 3)
;; => 13
(adder 3 2)
;; => 5
以上範例示範以一個參數和以兩個參數呼叫函式,由於根據參數而分開不同實作,所以結果並不相同。其中一個參數的版本還呼叫了自己的兩個參數的版本。
有時候在定義函式的時候並不清楚參數的個數,或是想要接受不定個數的參數,Clojure 提供方法在定義函式時,聲明接受的是不定長度的參數。
在函式定義的參數向量中,在參數名稱前加上 &
符號與空白,則之後的參數都會包裝至序列之中:
(defn str-all-numbers [x & rest]
(apply str "Hi " rest))
;; => #'user/str-all-numbers
(str-all-numbers 0 1 2 3 4)
;; => “Hi 1234”
(str-all-numbers 0)
;; => “Hi ”
在這個範例中,第一個呼叫 str-all-numbers
函式時代入了五個參數,x
爲第一個參數,其他參數則被裝進以 rest
命名的列表 (List) 中,由於只用到了後續的參數,所以第一個參數並不會被印出。
不定長度參數在 Clojure 中還具備了可選的屬性,亦即呼叫有不定長度參數的函式時,參數可以提供也可以不提供。第二次呼叫 str-all-numbers
函式時只給了第一個參數,後續的參數便不會被印出。
範例使用到的 apply
函式,第一個參數是函式,其後的參數將會被依序套用到第一個參數的函式中。
以下範例示範結合多載與不定長度參數的函式定義方法:
(defn overloading-variadic
([] 0)
([x] 1)
([x y] 2)
([x y & rest] "many arguments"))
;; => #'user/overloading-variadic
(overloading-variadic)
;; => 0
(overloading-variadic "one")
;; => 1
(overloading-variadic "one" "two")
;; => 2
(overloading-variadic "one" "two" "three")
;; => "many arguments"
這裡有一個函式,接受一個列表當作參數,參數的作用是將列表中的第二與第四個位置相加:
(defn useless-adder [lst]
(let [x (first (rest lst))
y (first (rest (rest (rest lst))))]
(+ x y)))
;; => #'user/useless-adder
(useless-adder [1 3 5 7 9])
;; => 10
看到層層 first
與 rest
是不是看到頭暈了呢?Clojure 提供了簡便的語法,可以更快速地取得參數的內容,稱爲解構 (Destructuring)。
向量解構可以使用在任何序列型的群集,例如列表、向量、字串或序列。如同前一個例子,使用向量解構取得第二與第三個元素,會變得非常簡單:
(let [[_ x _ y] [1 3 5 7 9]]
(+ x y))
;; => 10
這裡把不需要理會的元素以底線 (_) 來表示,需要取得的元素則賦予名字,根據擺放的位置匹配適當的元素。
巢狀向量中的元素也可以匹配:
(let [[_ _ [x y]] [1 2 [3 4] 5]]
(* x y))
;; => 12
可以在解構式最後加上 :as
繫結整個待解構的向量:
(let [[x y :as original] [1 2 3 4 5]]
(conj original (+ x y)))
;; => [1 2 3 4 5 3]
除了使用 :as
之外,還可以使用 &
來匹配其他未匹配的剩餘元素:
(let [[x & rest] [10 20 30 40 50]]
rest)
;; => (20 30 40 50)
當然也可以將 :as
與 &
兩個結合起來:
(let [[x & rest :as original] [2 4 6 8 10]]
(println "x:" x ", rest:" rest ", original:" original))
;; => x: 2 , rest: (4 6 8 10) , original: [2 4 6 8 10]
以上的範例使用了 println
將資料輸出到螢幕,並加上換行。
有了解構之後,函式的參數就可以輕鬆地取得對應的內容:
(defn useful-adder [[_ x _ y]]
(+ x y))
;; => #'user/useful-adder
(useful-adder [1 3 5 7 9])
;; => 10
跟一開始複雜的範例相比,是不是簡單很多呢。
解構映射也跟解構向量一樣,根據擺放的位置與索引鍵,匹配對應的資料:
(def m {:a 5 :b 10 "c" 15})
;; => #'user/m
(let [{a :a b :b} m]
(+ a b))
;; => 15
a
匹配索引鍵 :a
對應的資料、b
匹配索引鍵 :b
對應的資料。找不到對應的資料,會得到預設的 nil
,如果不想使用預設的 nil
,亦可以透過 :or
指定當某索引鍵找不到資料時,預設取得的資料:
(def m {:a 5 :b 10 "c" 15})
;; => #'user/m
(let [{a :a b :b d :d :or {d "OH"}} m]
(println a b d))
;; => 5 10 OH
以上範例試圖取用索引鍵 :d
對應的資料,並在 :or
提供一個映射,指定了當找不到對應的資料時,應該選用的預設資料。
在向量解構時使用到的 :as
也可以使用在這裏,唯一不同的是不需要擺放在最後位置 (但建議還是放在最後):
(let [{a :a b :b :as whole} m]
(println a b whole))
;; => 5 10 {:a 5, :b 10, c 15}
如果打算匹配的映射,其中的索引鍵都是由關鍵字組成的,Clojure 提供了 :keys
用來匹配映射中的關鍵字索引鍵。:keys
後加上一個向量,其中寫下打算匹配的關鍵字名稱,匹配後就可以透過名稱取得資料:
(def m {:a 10 :b 20 :c 15})
;; => #'user/m
(let [{:keys [a b]} m]
(println a b))
;; => 10 20
如果映射中的索引鍵都是使用字串則使用 :strs
、都是使用符號則用 :syms
:
(let [{:strs [a d]} {"a" "A", "b" "B", "c" "C", "d" "D"}]
(println a d))
;; => A D
(let [{:syms [a d]} {'a "A", 'b "B", 'c "C", 'd "D"}]
(println a d))
;; => A D
使用了映射解構的函式,就可以輕鬆地取出索引鍵代表的資料了:
(defn greet-user [{:keys [first-name last-name]}]
(println "Welcome," first-name last-name))
;; => #'user/greet-user
(def catherine {:first-name "Catherine", :last-name "Chen", :age 40})
;; => #'user/catherine
(greet-user catherine)
;; => Welcome, Catherine Chen
(未完待續)