Clojure 中的 for
與一般程式語言的 for
不同,它利用一個群集作爲來源,運用運算式以及條件式產生新的群集,這稱作列表推導 (List comprehension)。它接受類似 let
繫結綁定的方式,在向量中以符號繫結群集,之後的本體運算式將陸續被代入群集中的元素,本體運算式每次求值的結果則放入新的群集中,運行完畢後返回新的群集。
以下範例示範由代入的向量,產生向量中元素以及它的倍數組成一對的新向量:
(for [x [1 2 3 4 5]] [x (* x x)])
;; => ([1 1] [2 4] [3 9] [4 16] [5 25])
前面曾提到 doseq
運算式有幾個條件修飾子 (Modifier) 可以調整產生的結果,其實 doseq
是依靠 for
打造出來的,doseq
的修飾詞都是繼承自 for
,三種條件修飾子都是 for
提供給 doseq
。讓我們來看看 for
使用條件修飾子的樣貌:
(for [x (range 5)
y (range 5)
:let [z (+ x y)]
:when (.isProbablePrime (BigInteger/valueOf z) 5)]
(list x y))
;; => ((0 2) (0 3) (1 1) (1 2) (1 4) (2 0) (2 1) (2 3) (3 0) (3 2) (3 4) (4 1) (4 3))
以上範例中 x
與 y
分別代表內容包含 0 到 4 的列表,當兩個向量中的元素相加起來爲質數,便將兩元素放入新群集裡。範例中示範了如何使用 Java 靜態方法與呼叫 BigInteger 的執行個體方法。與 Java 的溝通將於後續的文章中詳細介紹。
Clojure 語言之中有非常多的巨集 (Macro),它擴充了語言基本提供的功能,帶來了易用與方便,在後續文章中將會有詳細的介紹。這裡先介紹的巨集,它爲程式碼帶來可讀性,稱爲穿引巨集 (Threading Macro)。
假設 Clojure 工程師年薪的 5 % 會拿來買書,其中的 30 % 則用來購買技術相關書籍,每年花在購買技術書籍的錢,可以用以下公式計算出來:
spending = ((salary * 0.05) * 0.3) * year
改寫成 Clojure 程式碼後,計算十年花費了多少錢:
(defn spending [salary year]
(* (* (* salary 0.05) 0.3) year))
(spending 10000 10)
;; => 1500.0
看起來運作的不錯,但是計算花費的函式本體一層又一層的計算,真不容易閱讀。首位穿引巨集 (Thread-first macro) 正是解決這個問題的好方法。首位穿引巨集的寫法看起來就像是箭頭,由一個橫線 (-) 加上大於符號 (>) 組成,首位穿引巨集中的第一個參數運算式求值完成後,結果會傳遞給下一個參數運算式的第一個參數。以下是改用首位穿引巨集之後的結果:
(defn spending [salary year]
(-> (* salary 0.05)
(* 0.3)
(* year)))
(spending 10000 10)
;; => 1500.0
一圖勝千言:
+-------------------+
| |
| * salary 0.05 +-----------+
| | |
+-------------------+ |
+-----+---v----+-----+
| | | |
| * | | 0.3 +-----------+
| | | | |
+-----+--------+-----+ |
+-----+---v---+------+
| | | |
| * | | year |
| | | |
+-----+-------+------+
修改過後的程式碼變得更容易閱讀。前一個運算式的結果,接上下一個運算式的第一個參數的位置,一目瞭然。
首位穿引巨集是將結果放在下一個運算式的第一個參數,而末位穿引巨集 (Thread-last macro) 則是將結果放在下一個運算式的最後一個參數,寫法爲一個橫線 (-) 加上兩個大於符號 (>)。
修改前的運算式如下所示:
(take 5 (map #(+ % 2) (range 10)))
;; => (2 3 4 5 6)
使用末位穿引巨集改寫之後:
(->> (range 10)
(map #(+ % 2))
(take 5))
;; => (2 3 4 5 6)
一圖勝千言:
+-------------+
| |
| range 10 +---------------------+
| | |
+-------------+ |
+-----+----------+--v--+
| | | |
| map | #(+ % 2) | +---------------+
| | | | |
+-----+----------+-----+ |
+------+---+--v--+
| | | |
| take | 5 | |
| | | |
+------+---+-----+
通過本篇文章,你了解了如何使用條件式來決定應該對哪個運算式求值,也了解到達成循環迭代功能的方法;還知道了強大的列表推導,可以生成列表。使用穿引巨集則可以讓程式易於閱讀和理解。
還不賴吧?今天就先到這裡,下一篇文章再見囉!
(本篇文章同步刊登於 GitHub,歡迎在文章下方留言或發送 PR 給予建議與指教)