鴨子划水中,不知不覺就完賽1/2了!(給自己一個讚)
狀態顯示為每天早上趕鐵人賽稿來不及吃早餐 XD
今天要講的是
thread-first macro (->) vs thread-last macro (->>)
其實呀!在第二天的文章的例子,就有默默地鋪梗了:
以下的程式代表求取50以內的偶數平方和:
(->> (range 50)
(filter even?)
(map (fn [x] (* x x)))
(reduce +))
=> 19600
我已經佈這個局佈了兩週之久 XD
而且更棒的是,上面提到的function (range / filter / even? / map / 匿名函數 / reduce)
現在是不是都看得懂了呢?
看不懂的也不要緊,可以再回去複習本系列前10篇文章 ;)
Macro是重組code的語法,
從這張流程圖(Reference)發現:
macro expansion time在reader之後
、compile(eval)之前
,會將code重組完成
至於什麼是->>
和->
呢?他們是怎麼重組code
待我們閱讀clojure doc後細細說明!
->>
和->
: (Threading Macro / Arrow Macro)->>
和->
也是Macro的一種
它們會幫忙把複雜的nested function calls
轉換成線性的流程 (linear flow of function calls),
讓人眼看起來是照code字面上順序執行的感覺
來比較API的說明:
->
(-> x & forms)
Threads the expr through the forms.
Inserts x as the **second** item in the first form, making a list of it if it is not a list already.
If there are more forms,
inserts the first form as the second item in second form, etc.
->>
(->> x & forms)
Threads the expr through the forms.
Inserts x as the **last** item in the first form,
making a list of it if it is not a list already.
If there are more forms,
inserts the first form as the last item in second form, etc.
->
為了舉例講解->
的例子,
在這裡介紹一個新的API出場:rest (請掌聲鼓勵鼓勵)
rest會回傳除了第一個之外、剩下的item sequence
rest
(rest coll)
Returns a possibly empty seq of the items after the first.
Calls seq on its argument.
來試試看:
(rest [1 2 3 4 5])
=> (2 3 4 5)
(rest nil)
=> ()
484剛好跟 first用途相反!
(first coll)
Returns the first item in the collection. Calls seq on its argument.
If coll is nil, returns nil.
;;拿出第一個
(first [1 2 3 4 5])
=> 1
(first nil)
=> nil
->
搭配 rest這裡有個1到6的vector,
(-> [1 2 3 4 5 6] reverse rest)
=> (5 4 3 2 1)
用 macroexpand
展開看看:
(macroexpand '(-> [1 2 3 4 5 6] reverse rest))
=> (rest (reverse [1 2 3 4 5 6]))
(rest (reverse [1 2 3 4 5 6]))
=> (5 4 3 2 1)
把reverse放在rest後面呢?
(-> [1 2 3 4 5 6] rest reverse)
=> (6 5 4 3 2)
用 macroexpand
展開看看:
(reverse (rest [1 2 3 4 5 6]))
(6 5 4 3 2)
(macroexpand '(-> [1 2 3 4 5 6] rest reverse))
=> (reverse (rest [1 2 3 4 5 6]))
->
取出再複雜一點的data structure以上用vector的例子比較不容易理解 ->
的威力
我們來個複雜點的map
(def person
{:name "Ting"
:address {:street "154 加蚋路"
:city "貓貓市"
:zip 520}
:employer {:name "Abagile"
:address {:street "華爾街"
:city "鴨鴨市"
:zip 888}}})
想知道Ting在哪裡上班的話,
我們可以用 ->
來表達inserts the first form as the second item in second form
(-> person :employer :address :street)
=> "華爾街"
->
實務上常用的地方->
常用來搭配 assoc
and update
data manipulation之類的function
(-> field
(name ,,,)
(keyword ,,,))
(-> :person
(assoc :hair-color :gray)
(update :age inc))
->
和->>
順序比較(重要!)以下的算式求出來的結果是 ((4 + 2) -1) * 3 = 15
(def number 4)
(-> number (+ 2) (- 1) (* 3))
15
如果換成->>
就完全不同囉
(def number 4)
(->> number (+ 2) (- 1) (* 3))
-15
(->> number (+ 2) (- 1) (* 3))
其實是
(* 3 (- 1 (+ 2 number)))
的意思
為什麼會這樣呢? 關鍵在那個 -1
,算出來的參數會插在後面
(- 1 (參數) )
所以以人類的肉眼來看是 (1 - (2 + 4)) * 3 = -15 (負15)
真是正負兩極呀~~~
'clojure.walk
拆解 macroexpand-all
執行順序如果還是不相信的話,可以使用macroexpand-all
展開箭頭macro的廬山真面目~
(use 'clojure.walk)
(macroexpand-all '(-> number (+ 2) (- 1) (* 3)))
=> (* (- (+ number 2) 1) 3)
;;
(macroexpand-all '(->> number (+ 2) (- 1) (* 3)))
=> (* 3 (- 1 (+ 2 number)))
->>
回到本文一開始的例子:求取50以內的偶數平方和
用昨天介紹的macroexpand展開:
tutorial.core=> (macroexpand '(->> (range 50)
#_=> (filter even?)
#_=> (map (fn [x] (* x x)))
#_=> (reduce +)))
會發現原型是我們一開始學的長長一條clojure語法
(reduce + (map (fn [x] (* x x)) (filter even? (range 50))))
翻譯蒟蒻:
是不是有點像pipelines(管線)一樣,一個步驟處理完接著下個步驟呢?
map
, filter
, remove
, reduce
, into
常常使用thread-last macro ->>
as->
在有些情況會需要用到 as->
,而不能用->>
和->
A pipeline may consist of function calls with varying insertion points. In these cases, you’ll need to use as->
the first argument is a value to be threaded through the following forms.
The second argument is the name of a binding
至於舉例的話~ 嘿嘿先賣個關子
同個箭牌系列的有好多,大家不要暈囉~
as->
cond->
cond->>
some->
some->>
明天都來一網打盡吧~!