看完 ClojuTRE 2015 的 Unknown pearls from the Clojure standard library - Renzo Borgatti 演講後,來紀錄個筆記。
這場演講介紹了一些在 clojure.core 裡面的函式,這些函式平常可能不太有機會用到,但是可以協助我們除錯程式的問題。
投影片: 下載連結
destructure
destructure (解構) 在 Clojure 裡面是個非常實用的功能,可以方便我們對資料直接指派變數去代替它,如下:
No destructuring
(let [data [1 2 3] a (nth data 0) b (nth data 1) c (nth data 2)] (println "a:" a "b:" b "c:" c)) ;; => a: 1 b: 2 c: 3
With destructuring
(let [[a b c] [1 2 3]] (println "a:" a "b:" b "c:" c)) ;; => a: 1 b: 2 c: 3
我們可以透過 destructure 去觀察一個東西是如何被解構的,這邊是投影片給的範例:
(destructure '[[x y & others] v]) ;; => [v2 v ;; x (nth v2 0 nil) ;; y (nth v2 1 nil) ;; others (nthnext v2 2)]
當然投影片給的是整理後的結果,實際上我執行得到的結果是這樣的
[vec__23596 v seq__23597 (clojure.core/seq vec__23596) first__23598 (clojure.core/first seq__23597) seq__23597 (clojure.core/next seq__23597) x first__23598 first__23598 (clojure.core/first seq__23597) seq__23597 (clojure.core/next seq__23597) y first__23598 others seq__23597]
reductions
我們在 Clojure 中很常用 reduce 去將一個函數作用到 list 上的每兩個元素上,然後返回最後的結果,最常見的簡單函數就是一個 list 的元素全部相加
(reduce + (range 10)) ;; => 45
而 reduce 的運作過程,則可以透過 reductions 來協助我們進行查看,可以看到這邊最後得到的 45 就是我們想要的結果。
(reductions + (range 10)) ;; => (0 1 3 6 10 15 21 28 36 45)
test
在 Clojure 中,我們可以在 metadata 中設定好對一個函數的測試方式,然後呼叫 test 對該函數進行測試,這項功能很適合用在小函式的一些 assertion 測試上。
(defn add+ {:test #(do (assert (= (add+ 2 3) 5)) (assert (= (add+ 4 4) 8)))} [x y] (+ x y)) (test #'add+) ;; <= trigger ;; => :ok
你也可以透過 meta 去查看你這個函式的 metadata 或是測試用的函式資訊
(meta #'addd+) ;; => {:arglists ([x y]), :test #function[hello.core/fn--23678], :line 350, :column 4, :file "/home/coldnew/Workspace/hello/src/hello/cpre.clj", :name add+, :ns #namespace[hello.core]}
clojure.pprint/cl-format
clojure.pprint/cl-format 是 Clojure 移植 Common Lisp 的 format 函式,對於同時寫 Clojure 和 ClojureScript 的開發者而言, cl-format 可以同時用於 Clojure 和 ClojureScript 上,方便了不少。
(註: CLJS-324 ISSUE 尚未被解決前,Clojure 的 format 是無法用於 ClojureScript 上的)
如果要更多關於 cl-format 的使用,可以看看 Praticle Common Lisp 一書,我在 clojure/clojurescript 與 left-pad 一文亦有提到如何透過 cl-format 實作 Clojure/ClojureScript 皆可以用的 leftpad 函式。
投影片上給的範例則是這樣:
(clojure.pprint/cl-format nil "~:r" 1234) ;; => one thousand, two hundred thirty-fourth (clojure.pprint/cl-format nil "~@r" 1234) ;; => MCCXXXIV
clojure.java.browse/browse-url
clojure.java.browse/browse-url 會呼叫系統預設的瀏覽器,開啟你所指定的網頁。
(clojure.java.browse/browse-url "http://localhost:3000")
clojure.java.javadoc/javadoc
Clojure 畢竟是 JVM 上的語言,有時候我們需要查看一些 javadoc,或是查看 Clojure 內部的 Java 實現,可以透過 clojure.java.javadoc/javadoc 來查看
(clojure.java.javadoc/javadoc (list* 1 [])) ;; => open clojure.lang.Cons Javadoc
clojure.reflect/reflect
老實說我看了還是不知道這是什麼,也許是和 Java 的 reflection 有關,不過我們還是可以在 clojure.reflect/reflect 的文檔中看出一些東西
(require '[clojure.reflect :refer [reflect]]) (require '[clojure.pprint :refer [print-table]]) ;; Here we have a simple function that prints the ;; important bits of the class definition in a table. (->> String reflect :members print-table) ;; => ;; | :name | :return-type | :declaring-class | :parameter-types | :exception-types | :flags | ;; |--------------------------+------------------------+------------------+--------------------------------------------------------+----------------------------------------+-------------------------------| ;; | replaceAll | java.lang.String | java.lang.String | [java.lang.String java.lang.String] | [] | #{:public} | ;; | CASE_INSENSITIVE_ORDER | | java.lang.String | | | #{:public :static :final} | ;; | indexOf | int | java.lang.String | [char<> int int char<> int int int] | [] | #{:static} | ;; | codePointCount | int | java.lang.String | [int int] | [] | #{:public} | ;; | getChars | void | java.lang.String | [int int char<> int] | [] | #{:public} | ;; | regionMatches | boolean | java.lang.String | [int java.lang.String int int] | [] | #{:public} | ;; | isEmpty | boolean | java.lang.String | [] | [] | #{:public} | ;; | codePointAt | int | java.lang.String | [int] | [] | #{:public} | ;; | lastIndexOf | int | java.lang.String | [java.lang.String] | [] | #{:public} | ;; | startsWith | boolean | java.lang.String | [java.lang.String int] | [] | #{:public} | ;; ...etc
講者在投影片中給的範例則是這個: (注意到結果是節錄呦~)
(require '[clojure.reflect :refer [reflect]]) (println (with-out-str (clojure.pprint/write (reflect :a)))) ;; extract from a typical output: {:name invoke, :return-type java.lang.Object, :declaring-class clojure.lang.Keyword, :parameter-types [java.lang.Object java.lang.Object], :exception-types [], :flags #{:public :final}}