我們在第一週簡單地帶大家Clojurescript的基礎語法,有些篇幅也把clojure和跟Javascript或其他語言做對照,讓大家更易上手、也搞懂常見定義function的方式及資料結構:
Day01 探索Clojure / Clojurescript 開賽宣言
Day02 學習Clojure語法的勇敢與真實
Day03 Javascript vs Clojurescript - def & defn 比較
Day04 Clojure data structure之collection - Vectors
Day05 Clojure data structure之collection - Lists
Day06 Clojure data structure之collection - Maps
Day07 Clojure data structure之collection - Sets
接下來幾天就focus在Functional Programming的基礎函式運用 -
三法寶: map / reduce / filter
並且也要注意這些函式會回傳哪一種資料結構喔!
我們不要把今天的map函式
和第六天的 Map資料結構
搞混囉!(map function通常用小寫表示)
首先從數字number的好用的API叫inc
使用方式是 (inc x)
,會回傳x +1的數字 (Returns a number one greater than num.)
(inc 1)
=> 2
(inc 3.1)
=> 4.1
如果是想對一個vector裡的每一個數字 +1呢?
(inc [1 2 3])
=> Execution error (ClassCastException)
class clojure.lang.PersistentVector cannot be cast to class java.lang.Number (clojure.lang.PersistentVector is in unnamed module of loader 'app'; java.lang.Number is in module java.base of loader 'bootstrap')
ClassCastException說明了vector無法轉換成 numbers
用map來改裝一下,就ok了!
至於為什麼會這樣呢?
來閱讀一下map function的說明Ref
並且試著進一步去拆解:
在我們舉的例子裡,
(map inc [1 3 5 7 9])
; => (2 4 6 8 10)
對照一下是說明的:(map f c1)
在(map inc [1 3 5 7 9])
裡collection只有一組,map函式會對每個在collection的element套用某個function
例如inc
,map讓vector裡的每個數字 +1,並回傳一個看起來很像list
的東東
但又不確定是什麼?
那我們可以用class
來查查看,這個是LazySeq
(class (map inc [1 2 3]))
=> clojure.lang.LazySeq
;;跟一般的list不一樣
(class (list 1 2 3))
=> clojure.lang.PersistentList
注意,map不會回傳 vector
喔!
在Day03 Javascript vs Clojurescript - def & defn 比較時有聊果
這裡我們想介紹匿名函式的簡短寫法 #()
(fn [X Y] (str X Y))
和
#(str %1 %2)
是一樣的
再搭配map的話,這樣我們就可以對Vector裡的一個一個的值做匿名函數裡想要的操作
例如把字串串起來。
來跟我家兩隻貓貓們說早安吧!
(map #(str "Good Morning, " %) ["Goma" "Cara"])
=> ("Good Morning, Goma" "Good Morning, Cara")
貓貓:我們還想繼續睡!
在第二天講解過了最簡單的(+ 1 1)
。+1
這樣的函式當然可以自己實作囉
(def increment (fn[x] (+ x 1)))
=> #'tutorial.core/increment
(increment 3.1)
=> 4.1
;; def 換成 defn寫法
(defn increment-f [x] (+ x 1))
=> #'tutorial.core/increment-f
(increment-f 3.1)
=> 4.1
如同最上面的 inc
例子,
假設我們有自己寫好的function times-20
(乘以20倍),
若想要對vector的每個element操作相同的函式,
也是可以照著手冊上說的 (map f col)
來使用
(def times-20 (fn[x] (* x 5 4)))
;; 用map做出一個function
(defn multiple_vector
[]
(map times-20 [1 3 5 7 9]))
tutorial.core=> (multiple_vector)
=> (20 60 100 140 180)
從前面inc / increment / 到剛剛的 times-20
例子,
恰好說明了一級函式(first-class function)的特性,
函式是頭等公民,可被當成是值
, (functions can be treated as values)
這個頭等公民超厲害的,有多項特色:
以這兩個例子來說,
(+ 1 2 ...n)
(* 5 4 ...n)
(+ 1 2 ...n)
的 +
函式代表對第一個數字進行加法,計算完後再丟給下一個去加(* 5 4 ...n)
的 *
函式代表對第一個數字都進行乘法,計算完後再丟給下一個去乘
(+ 1 2 3 4...)
這樣的寫法和其他語言的 (1 + 1) 寫法很不同,
也讓clojure的+
function後面可以接很多個參數!
某位同事的這篇文章就說難怪clojure沒有其他語言的sum,因為+
就是sum
啦
在clojure裡,=+
也是個function,-
也是個functioninc
和map
也是function,這些和自定義(defn)的function是同個level的。function眾生平等。
All Functions Are Created Equal
One final note: Clojure has no privileged functions. + is just a function, - is just a function, and inc and map are just functions. They’re no better than the functions you define yourself. So don’t let them give you any lip!
Ref:braveclojure Chapter3 do things
前面介紹了Numbers的inc
function,
接下來用給String的 str
舉例map後面接2個collection
(map f c1 c2 c3)
如果再重新複習這段定義的話,
map returns a lazy sequence consisting of the result of applying f to
the set of first items of each coll, followed by applying f to the
set of second items in each coll, until any one of the colls is
exhausted. Any remaining items in other colls are ignored. Function
f should accept number-of-colls arguments.
(map str ["a" "b" "c"] ["x" "y" "z"] ["A" "B" "C"])
=> ("axA" "byB" "czC")
來看map搭配3個collection的分解動作(在以上的例子是以vector為例):
1.先用str把collection的第一個值接起來
編按:
原本在想是否用(str(str(str "a") "x") "A")
這樣表示 (clojure core library內str實作的效果)
但本篇文章若要強調map function特性,一次過丟所有參數進去呼叫function來表達的話,
這裡稍稍修改一下成(str "a" "x" "A")
比較好,感謝路過的大大指點)
(str "a" "x" "A")
=> "axA"
接著把collection的第二個值接起來
(str "b" "y" "B")
=> "byB"
接著把collection的第三個值接起來
(str "c" "z" "C")
=> "czC"
2.最後map會傳回一個新的list
(map str ["a" "b" "c"] ["x" "y" "z"] ["A" "B" "C"])
=> ("axA" "byB" "czC")
function是一等公民
和每個function眾生平等
的概念真的好特別,值得多看code、多練習一點題目唷!
明後天繼續介紹也是超級常用的reduce及filter~