Unknown perls from the Clojure standard library 筆記

栏目: 编程语言 · Clojure · 发布时间: 6年前

内容简介:Unknown perls from the Clojure standard library 筆記

看完 ClojuTRE 2015Unknown 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}}

clojure.inspector/inspect-tree

我們在處理一些樹狀資料時(ex: JSON 格式),有個圖示化的 工具 可以方便瀏覽所有資訊,這邊可以透過 clojure.inspector/inspect-tree 來查看

(require '[clojure.inspector :as i])

(def m {:a "a"
        :b {:c "c"
            :d [1 2 3]
            :e {:f "f"
                :g "g"
                :h "h"}}
        :i [1 2 3]
        :l {:m "m"
            :n "n"}
        :o [{:p "p" :q "q"}]})

(i/inspect-tree m)

這樣呼叫,會得到如下的視窗

Unknown perls from the Clojure standard library 筆記

clojure.lang.PersistentQueue

clojure.lang.persistentQueue 是 Clojure 下並未寫在 doc 上面的 Queue 實現 (java) ,我們可以用它來實作我們需要的 queue (佇列) 功能

基本上,你有以下幾種方法可以對你的 PersistentQueue 進行處理

  • peek

    取得 queue 最頂端的資料(head)

  • pop

    回傳不包含最頂端資料(head)的一個新的 PersistentQueue

  • conj

    將資料加入到 queue 的尾巴

  • empty?

    檢測 queue 是否為空的

  • seq

    將 queue 的資料變成序列 (sequence)

投影片上,講者是舉這範例:

(def e (clojure.lang.PersistentQueue/EMPTY))
(def buf (reduce conj e (range 10)))

(peek buf)
;; => 0

(peek (pop buf))
;; => 1

(peek (pop (pop buf)))
;; => 2

StackOverflow 則是有人舉出了如何透過 clojure.lang.persistentQueue 實現自己的 queue 函式

(defn queue
  ([] clojure.lang.PersistentQueue/EMPTY)
  ([coll] (reduce conj clojure.lang.PersistentQueue/EMPTY coll)))

(defmethod print-method clojure.lang.PersistentQueue
  [q ^java.io.Writer w]
  (.write w "#queue ")
  (print-method (sequence q) w))

(comment
  (let [*data-readers* {'queue #'queue}]
    (read-string (pr-str (queue [1 2 3])))))

fnil

我們在用 Clojure 處理東西的時候,有時候可能是資料本身就是 nil 的情況,這種狀況下對 nil 進行處理可能就會導致 Exception 的狀況發生

在傳統的 LISP 中,常以 or 作為一個保護,一旦遇到 nil 的情況,則回傳預設值

(or nil 10)
;; => 10

fnil 則是用來替你的函式多加一層保護,讓函式遇到 nil 的狀況可以避免一些 Exception 的發生

講者在投影片上提供的範例是這樣的:

(def m {:host "127.0.0.1" :port nil})
(update m :port (fnil #(Integer/parseInt %) "80"))
;; => {:host "127.0.0.1", :port 80}

(def m {:host "127.0.0.1" :port "8008"})
(update m :port (fnil #(Integer/parseInt %) "80"))

;; => {:host "127.0.0.1", :port 8008}

不過我們也可以將其拆開來看,這樣會更好理解

(Integer/parseInt "10")
;; => 10

(Integer/parseInt nil)
;; =>  Unhandled java.lang.NumberFormatException

((fnil #(Integer/parseInt %) 80) "1000")
;; => 1000

counted?

counted? 是 O(1) 的操作,用來查看目標是否具有實作 count 函式

(counted? [:a :b :c])
;; => true

(counted? '(:a :b :c))
;; => true

(counted? {:a 1 :b 2 :c 3})
;; => true

(counted? #{:a :b :c})
;; => true

(counted? "asdf")
;; => false

(counted? (into-array Integer/TYPE [1 2 3]))
;; => false

reversible?

reversible? 也是 O(1) 的操作,用來查看目標是否有實作 reversible 函式

(reversible? [])
;; =>  true

(reversible? (sorted-map))
;; =>  true

(reversible? (sorted-set))
;; =>  true

(reversible? '())
;; =>  false

(reversible? {})
;; =>  false

(reversible? #{})
;; =>  false

vector-of

vector-of 會根據你提供的類型 (permitive type) 來將參數轉換成相對應型別的向量 (vector)

可以使用的類型有 :int , :long , :float , :double , :byte , :short , :char , :boolean

(conj (vector-of :int) 1 2 3)
;; => [1 2 3]  ; <-- note, these are unboxed internally

(vector-of :int 1 2 3)
;; => [1 2 3]  ; same here

(vector-of :float 1 2 3)
;; => [1.0 2.0 3.0]

(type (conj (vector-of :int) 1 2 3))
;; => clojure.core.Vec

(type (conj (vector-of :float) 1 2 3))
;; => clojure.core.Vec

clojure.set/rename-keys

clojure.set/rename-keys 可以用來改變目前現有的 hash-map 的關鍵字 (keyword) 名稱

(require 'clojure.set)

(clojure.set/rename-keys {:a 1, :b 2} {:a :new-a, :b :new-b})
;; => {:new-a 1, :new-b 2}

(cloure.set/rename-keys {:a 1} {:b :new-b})
;; => {:a 1}

;; You need to be careful about key collisions.  You probably shouldn't
;; depend on the exact behavior.
(clojure.set/rename-keys {:a 1 :b 2} {:a :b})
;; => {:b 1}

(clojure.set/rename-keys  {:a 1 :b 2}  {:a :b :b :a})
;; => {:a 1}

clojure.data/diff

clojure.data/diff 用來對兩個序列(sequence)進行比較,並回傳比較的結果

(require 'clojure.data)

(clojure.data/diff {:a 1 :b 2} {:a 3 :b 2 :c 3})
;; => ({:a 1} {:a 3, :c 3} {:b 2})

(clojure.data/diff [1 2 3] [5 9 3 2 3 7])
;; => [[1 2] [5 9 nil 2 3 7] [nil nil 3]]

(clojure.data/diff (set [1 2 3]) (set [5 9 3 2 3 7]))
;; => [#{1}  #{7 9 5}        #{3 2}]

munge

munge 這個函式並未有文檔,因此只能實際看看用途了

(defn foo [] (println "foo"))
;; =>  #'user/foo

foo
;; => #<user$foo user$foo@a0dc71>

(munge foo)
;; => "user_DOLLARSIGN_foo_CIRCA_a0dc71"


(doseq [c (remove #(Character/isLetterOrDigit %) (map char (range 32 127)))]
  (println c "->" (munge c)))
;; Prints:
;;   ->
;; ! -> _BANG_
;; " -> _DOUBLEQUOTE_
;; # -> _SHARP_
;; $ -> $
;; % -> _PERCENT_
;; & -> _AMPERSAND_
;; ' -> _SINGLEQUOTE_
;; ( -> (
;; ) -> )
;; * -> _STAR_
;; + -> _PLUS_
;; , -> ,
;; - -> _
;; . -> .
;; / -> _SLASH_
;; : -> _COLON_
;; ; -> ;
;; < -> _LT_
;; = -> _EQ_
;; > -> _GT_
;; ? -> _QMARK_
;; @ -> _CIRCA_
;; [ -> _LBRACK_
;; \ -> _BSLASH_
;; ] -> _RBRACK_
;; ^ -> _CARET_
;; _ -> _
;; ` -> `
;; { -> _LBRACE_
;; | -> _BAR_
;; } -> _RBRACE_
;; ~ -> _TILDE_

gensym

gensym 會產生不衝突名稱的 symbol,這個應該是在 macro 實現時,Clojure 所使用到的函式

(gensym "foo")
;; => foo2020

(gensym "foo")
;; => foo2027

seque

seque 是一種 阻塞隊列(Linked Blocking Queue, LBQ) 的實作,這種實現是線程安全(thread safe)的,可以確保資料先進先出(first in first out, FIFO) 的狀況。

(let [start (System/nanoTime)
      q (seque
         (iterate
          #(do (Thread/sleep 400) (inc %))
          0))]
  (println "sleep five seconds...")
  (Thread/sleep 5000)
  (doseq [i (take 20 q)]
    (println (int (/ (- (System/nanoTime) start) 1e7))
             ":" i)))


;; The iterate form returns a lazy seq that delays nearly a half-second
;; before returning each subsequent item.  Here seque starts a thread
;; generating the lazy seq.

;; The body of the let allows the seque thread to get ahead by five seconds
;; before it begins consuming the seq using doseq.  The doseq prints a
;; timestamp and the value from the seq when it becomes available.  The
;; first 11 or so are available almost instantly, until the consuming
;; doseq catches up with the producing iterate, at which point the consumer
;; blocks for 400ms before each item can be printed.

;;sleep five seconds...
500 : 0
500 : 1
500 : 2
500 : 3
500 : 4
500 : 5
500 : 6
500 : 7
500 : 8
500 : 9
500 : 10
500 : 11
520 : 12
;; ......

clojure.zip/zippers

clojure.zip 是用來處理樹狀結構用函式庫,這篇文章有很好的解釋: (λx.Liu)Blog=Hacking: Understand clojure.zip with picture

clojure.zip/zipper 則是用來將資料包裝起來,這樣我們就可以透過 clojure.zip 來處理這些樹狀資料

(def zp (clojure.zip/zipper vector? seq (fn [_ c] c)
                            [[1 2 3] [:a :b] 2 3 [40 50 60]]))
;; => [[[1 2 3] [:a :b] 2 3 [40 50 60]] nil]


(clojure.zip/down zp)
;; =>
[[1 2 3]
 {:l [],
  :pnodes [[[1 2 3] [:a :b] 2 3 [40 50 60]]],
  :ppath nil,
  :r ([:a :b] 2 3 [40 50 60])}]


(-> zp
    clojure.zip/down
    first)
;; =>
[1 2 3]


(-> zp
    clojure.zip/down
    clojure.zip/right)
;; =>
[[:a :b]
 {:l [[1 2 3]],
  :pnodes [[[1 2 3] [:a :b] 2 3 [40 50 60]]],
  :ppath nil,
  :r (2 3 [40 50 60])}]

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Haskell School of Expression

The Haskell School of Expression

Paul Hudak / Cambridge University Press / 2000-01 / USD 95.00

Functional programming is a style of programming that emphasizes the use of functions (in contrast to object-oriented programming, which emphasizes the use of objects). It has become popular in recen......一起来看看 《The Haskell School of Expression》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

html转js在线工具
html转js在线工具

html转js在线工具