22[模塊]Clojure常用模塊

Clojure常用模塊 - fxjwind - 博客園
http://www.cnblogs.com/fxjwind/archive/2013/06/04/3117544.html

http://qiujj.com/static/clojure-handbook.html
http://clojure.github.io/clojure/
Base

->, (-> x form & more)
http://clojuredocs.org/clojure_core/clojure.core/-%3E
線性化嵌套, 使其更具有可讀性, Inserts x as the second item in the first form
從下面的例子可以看出, 就是把第一個參數(x)作為最初的輸入, 調用第二個參數(代表的fn), 然后拿返回值調用后續函數
和..用處差不多, 但..只能用于java調用
;; Arguably a bit cumbersome to read:user=> (first (.split (.replace (.toUpperCase "a b c d") "A" "X") " "))"X";; Perhaps easier to read:user=> (-> "a b c d" .toUpperCase (.replace "A" "X") (.split " ") first)"X"

->> , (->> x form & more)
http://clojuredocs.org/clojure_core/clojure.core/-%3E%3E
Inserts x as the last item in the first form
和->的差別在于x插入的位置不同, ->是插入在第二個item, 即緊跟在函數名后面, 而->>是插在最后一個item
;; An example of using the "thread-last" macro to get;; the sum of the first 10 even squares.user=> (->> (range) (map #(* % %)) (filter even?) (take 10) (reduce +))1140;; This expands to:user=> (reduce + (take 10 (filter even? (map #(* % %) (range)))))1140

comp, (comp f1 f2 f3 & fs)
以一組函數為參數, 返回一個函數, 如例子my-fn 使用my-fn的效果就是, my-fn的參數個數等于fs所需的參數個數, 因為實際做法就是拿my-fn的參數調用fs, 然后用fs的返回值調用f3…一直繼續 所以除了fs以外的函數, 都必須只包含一個參數, 所以經常使用partial來減少參數個數, 配合使用
user=> (def my-fn (comp (partial * 10) - ))user=> (my-fn 5 3) ; 10(-(5*3))-150

if-let, when-let
對let添加if判斷, 如下面的例子, 如果nums非false或nil, 則執行累加, 否則表示list中沒有偶數打印"No even numbers found." 適用于對于不同的let結果的不同處理
user=> (defn sum-even-numbers [nums] (if-let [nums (seq (filter even? nums))] (reduce + nums) "No even numbers found."))user=> (sum-even-numbers [1 3 5 7 9])"No even numbers found."user=> (sum-even-numbers [1 3 5 7 9 10 12])22

when-let, 一樣的理論, 當let賦值非false或nil時, 執行相應邏輯, 否則返回nil
(defn drop-one [coll] (when-let [s (seq coll)] (rest s)))user=> (drop-one [1 2 3])(2 3)user=> (drop-one [])nil

cond, condp, case
cond, 替代多個if
(cond (< 0 n ) "n>0" (< 10 n) "n>10" :else "n <=0") ;;:else只是習慣寫法, 任意true都可以

condp, 簡化cond, <n只需要寫一遍 默認會將,0,10作為, 函數<的第一個參數, 即(< 0 n), (< 10 n) 最后一行默認為:else
(condp < n 0 "n>0" 10 "n>10" "n<=0")

case, 支持多選或不同類型
(case x 1 10 2 20 3 30 0)(case x (5 10) "5" (3 6 9) "3" "others")(case x "JoC" :a-book :eggs :breakfast 42 (+ x 100) [42] :a-vector-of-42 "The default")

defnk
和普通defn的不同是, 可以在參數里面使用k,v, 并且可以在函數體中直接使用k來得到value 其實它的實現就是增加一個hashmap來存放這些k,v
user> (use 'clojure.contrib.def)niluser> (defnk f [:b 43] (inc b))#'user/fuser> (f)44user> (f :b 100)101user=> (defnk with-positional [foo :b 43] (+ foo (inc b)))#'user/with-positionaluser=> (with-positional 5 :b 1)7

Collection操作
'(a b :name 12.5) ;; list['a 'b :name 12.5] ;; vector{:name "Chas" :age 31} ;; map#{1 2 3} ;; set

General
(first '(:alpha :bravo :charlie)) ;;:alpha(rest [1 2 3 4 5]) ;;(2 3 4 5),無論輸入,都是返回seq(rest [1]) ;;(),返回空seq, 而next會返回nil(cons 1 '(2 3 4 5 6)) ;;(1 2 3 4 5 6), (cons x seq), 將單個x加入seq, 多用conj代替(conj [:a :b :c] :d :e :f :g) ;;[:a :b :c :d :e :f :g],將后面多個elem逐個加入col(seq {:a 5 :b 6}) ;;([:a 5] [:b 6]), 將各種coll轉化為seq(count [1 2 3]) ;;= 3(reverse [1 2 3 4]) ;;(4 3 2 1)(interleave [:a :b :c] [1 2]) ;;(:a 1 :b 2)

(every? empty? ["" [] () '() {} #{} nil]) ;;true, 判斷是否為空
(map empty [[\a \b] {1 2} (range 4)]) ;;([] {} ()), 清空(
def
not-empty?
(
complement
empty?
)
) ;;(complement f),
(not-empty? []) –> false, 取反

(range start? end step?)(range 10) ;;(0 1 2 3 4 5 6 7 8 9)(range 1 25 2) ;;(1 3 5 7 9 11 13 15 17 19 21 23)

(repeat 5 1) ;;(1 1 1 1 1)
(take 10 (iterate inc 1)) ;;(1 2 3 4 5 6 7 8 9 10), iterate和cycle都是返回無限隊列, 所以需要take
(take 10 (cycle (range 3))) ;;(0 1 2 0 1 2 0 1 2 0)

(group-by count ["a" "as" "asd" "aa" "asdf" "qwer"]) ;;{1 ["a"], 2 ["as" "aa"], 3 ["asd"], 4 ["asdf" "qwer"]}, group-by f coll
(sort > [42 1 7 11]), (42 11 7 1) ;;默認是升序, 這里改成降序

(sort-by #(.toString %) [42 1 7 11]) ;;按str比較,所以7>42,(1 11 42 7)
(filter even? (1 2 3 4 5 6)) ;;(2 4 6)

(split-at 2 [1 2 3 4 5]) ;;[(1 2) (3 4 5)], (split-at n coll)
(split-with (partial >= 3) [1 2 3 4 5]) ;;[(1 2 3) (4 5)], (split-with pred coll), 在第一個不滿足pred的地方split(partition 4 [0 1 2 3 4 5 6 7 8 9]) ;;((0 1 2 3) (4 5 6 7))(partition-all 4 [0 1 2 3 4 5 6 7 8 9]) ;;((0 1 2 3) (4 5 6 7) (8 9)) ;;lazy,并不去尾(partition 4 2 "pad" (range 10)) ;;((0 1 2 3) (2 3 4 5) (4 5 6 7) (6 7 8 9) (8 9 \p \a)), 加上step和pad

Set
(union #{1 2} #{2 3}) ;;#{1 2 3}
(intersection #{1 2} #{2 3}) ;;#{2}
(difference #{1 2} #{2 3}) ;;#{1}(disj #{1 2 3} 3 1) ;;#{2}, 刪除

Vector
(nth [:a :b :c] 3) ;;= java.lang.IndexOutOfBoundsException, 等于([:a :b :c] 3)(get [:a :b :c] 3) ;;nil,和nth的不同

**stack **clojure中需要注意, list, 是stack邏輯(LIFO), 而vector是queue的邏輯(FIFO)
(conj [] 1 2 3) ;[1 2 3](conj '() 1 2 3) ;(3 2 1)(first (conj '() 1 2 3)) ;3(first (conj [] 1 2 3)) ;1

但是也可以讓vector, 表現出stack邏輯, 用pop和peek
(pop (conj [] 1 2 3)) ;[1 2], 和rest不同(peek (conj [] 1 2 3)) ;3, 和first不同

對于list, peek和pop就等同于first,rest

Hashmap
(assoc map key val) ;;add kv
(dissoc map key) ;;remove kv
(keys {:sundance "spaniel", :darwin "beagle"}) ;;(:sundance :darwin)(vals {:sundance "spaniel", :darwin "beagle"}) ;;("spaniel" "beagle")
(get {:sundance "spaniel", :darwin "beagle"} :darwin) ;; "beagle"
(select-keys map keyseq) ;;get多個key,(
select-keys
{
:a
1
:b
2
}
[
:a
:c
]
)
{
:a
1
}

into, (into to from)
把from join到to, 可以看到底下對于list, vector, set, 加完的順序是不同的, 剛開始有些疑惑 其實Into, 只是依次從from中把item讀出, 并append到to里面, 最終的順序不同因為數據結構對append的處理不同
; Adds a list to beginning of another. Note that elements of list are added in reverse since each is processed sequentially.(into '(1 2 3) '(4 5 6))=> (6 5 4 1 2 3)

(into [5 6 7 8] '(1 2 3 4))=> [5 6 7 8 1 2 3 4] (into #{5 6 7 8} [1 2 3 4])=> #{1 2 3 4 5 6 7 8}

merge, (merge & maps)
把多個map merge在一起, 如果有一樣的key則latter優先原則, 后出現的優先 user=> (merge {:a 1 :b 2 :c 3} {:b 9 :d 4}){:d 4, :a 1, :b 9, :c 3}

merge-with, (merge-with f & maps)
普通merge只是val的替換, 而merge-with可以使用f來merge, 比如下面的例子就是用+
;; merge two maps using the addition functionuser=> (merge-with + {:a 1 :b 2} {:a 9 :b 98 :c 0}) {:c 0, :a 10, :b 100}

apply, map, reduce, for
apply, (apply f args)
作用就是將args作為f的參數, 并且如果有collection, 會將elem取出作為參數
(apply f e1 [e2 e3]) ;; (f e1 e2 e3)(apply max [1 3 2]) ;; (max 1 3 2)

(
apply

1
2
'(
3
4
)
)
;; (+ 1 2 3 4))

map,
(map f [a1 a2..an]) ;; ((f a1) (f a2) .. (f an))
(map f [a1 a2..an] [b1 b2..bn] [c1 c2..cn]) ;; ((f a1 b1 c1) (f a2 b2 c2) .. (f an bn cn))
mapcat, (mapcat f & colls)
和普通map不同的是, 會對map執行的結果執行concat操作 等于(apply concat (map f &colls)) ;;注意apply的作用
user=> (mapcat reverse [[3 2 1 0] [6 5 4] [9 8 7]])(0 1 2 3 4 5 6 7 8 9)

reduce, (reduce f coll) or (reduce f val coll)
(reduce f [a b c d ... z])
(reduce f a [b c d ... z])
就是:
(f (f .. (f (f (f a b) c) d) ... y) z)
和apply的不同,
(reduce + [1 2 4 5]) ;; (+ (+ (+ 1 2) 4) 5)
(apply + [1 2 4 5]) ;; (+ 1 2 4 5)
for, (for seq-exprs body-expr)
for, 類似于python的list comps, 用于簡化map, filter 兩部分, 第一部分是seq-exprs, 列出lazy seq, 并且后面可以跟:let, :when, :while等定義和條件, 如下面的例子 第二部分是body-expr, 取出前面定義的lazy seq的每個元素執行body-expr, for返回的就是所有元素執行結果的list, 參考下例, 如果有多個lazy seq的話, 會窮盡組合
user=> (for [x [0 1 2 3 4 5] :let [y (* x 3)] :when (even? y)] y)(0 6 12)

user=> (for [x ['a 'b 'c] y [1 2 3]] [x y])([a 1] [a 2] [a 3] [b 1] [b 2] [b 3] [c 1] [c 2] [c 3])

但是需要注意的是, for返回的只是lazy seq, 所以如果需要確保body-expr在每個元素上都得到執行, 必須加dorun或doall

doall, dorun
doall和dorun都用于force lazy-seq, 區別在于
doall會hold head, 并返回整個seq, 所以過程中seq保存在memory中, 注意outofmemory dorun不會hold head, 遍歷run, 最終返回nil
(doall (map println [1 2 3]))123(nil nil nil)(dorun (map println [1 2 3]))123nil

doseq
doseq, 其實就是支持dorun的for(list comprehension), 和for語法基本一致 for返回的是lazy-seq, 而doseq = dorun (for…)
(doseq [x (range 7) y (range x) :while (odd? x)] (print [x y]))(for [x (range 7) y (range x) :while (odd? x)] [x y])

user=> (doseq [x [1 2 3] y [1 2 3]] (prn (* x y)))123246369nil

并發STM
ref, 多個狀態的協同更新(transaction)
(def v1 (ref 10))(deref v1) ;;@v1(dosync (ref-set v1 0)) ;;update(dosync (ref-set v1 (inc @v1)))(dosync (alter v1 inc)) ;;alter, read-and-set,后面跟函數(dosync (alter v1 + 10))

atom, 單個狀態的非協同更新
(def v1 (atom 10))(reset! v1 20) ; @v1=20 ;;單個值,所以不需要dosync來保證transaction(swap! v1 + 3) ; @v1=23 ;;read-and-set(def v2 (atom {:name "qh" :age 30}))(swap! v2 assoc :age 25) ; @v2={:name "james" :age 25

Java調用
先看下最常用的對應表,


image

(class "foo") ;;java.lang.String
(instance? String "foo") ;;true
(defn length-of [^String text] (.length text)) ;;Type Hinting

gen-class
http://clojure.org/compilation
解決compile ahead-of-time (AOT)問題, clojure作為動態語言, 會在runtime的時候compile并跑在JVM上, 但是某些時候需要提前compile并產生class 比如, deliver時沒有源碼, 或希望你的clojure代碼可以被Java調用...
Clojure compiles all code you load on-the-fly into JVM bytecode, but sometimes it is advantageous to compile ahead-of-time (AOT). Some reasons to use AOT compilation are:
To deliver your application without source
To speed up application startup
To generate named classes for use by Java
To create an application that does not need runtime bytecode generation and custom classloaders

解決這個問題的方法就是使用gen-class, 往往配合ns使用, 這樣會自動為該namespace生成class(省去:name)
(ns clojure.examples.hello (:gen-class))

在Storm里面的例子, DefaultScheduler實現接口IScheduler, 接口實現函數有'-'前綴, 如'-schedule’
(ns backtype.storm.scheduler.DefaultScheduler (:gen-class :implements [backtype.storm.scheduler.IScheduler]))(defn -prepare [this conf] )(defn -schedule [this ^Topologies topologies ^Cluster cluster] (default-schedule topologies cluster))

memfn, (memfn name & args)
Java中, 方法調用, file.isDirectory() 但對于clojure, 函數是first class, 所以調用方式為isDirectory(file)
問題是, 我在clojure里面使用Java類函數時, 也想以first class的方式, 那么就需要memfn來轉化
user=> (def files (file-seq (java.io.File. "/tmp/")))user=> (count (filter (memfn isDirectory) files))68user=> (count (filter #(.isDirectory %) files))68

可以看到其實是調用files.isDirectory(), 但通過memfn, 看上去好像是使用isDirectory(files) 直接看下這個macro的實現, 就是把memfn(name, args)轉化為target.name(args)
(defmacro memfn "Expands into code that creates a fn that expects to be passed an object and any args and calls the named instance method on the object passing the args. Use when you want to treat a Java method as a first-class fn." {:added "1.0"} [name & args] `(fn [target# ~@args] (. target# (~name ~@args))))

satisfies? , (satisfies? protocol x)
Returns true if x satisfies the protocol, 其實就是判斷x是否實現了protocol 如下列, number只extend了protocol Bar, 而沒有extend Foo
(defprotocol Foo (foo [this]))(defprotocol Bar (bar [this]))(extend java.lang.Number Bar {:bar (fn [this] 42)})(satisfies? Foo 123) ; => false(satisfies? Bar 123) ; => true

Test&Logging
Test
兩種形式,
deftest
其實就是創建函數, 象普通函數一樣去調用定義的test fn
(deftest test-foo (is (= 1 2)))(test-foo) ;;nil, pass沒有輸出(deftest test-foo (is (= 1 2)))(test-foo) ;;failFAIL in (test-foo) (NO_SOURCE_FILE:2)expected: (= 1 2) actual: (not (= 1 2))

with-test
這種方法, 把testcase加在metadata里面, 類似python的doctest 不影響函數的正常使用, 如下
(with-test (defn hello [name] (str "Hello, " name)) (is (= (hello "Brian") "Hello, Brian")) (is (= (hello nil) "Hello, nil")))

(hello "Judy") ;;"Hello, Judy"
((:test (meta #'hello)))FAIL in clojure.lang.PersistentList$EmptyList@1 (NO_SOURCE_FILE:5)expected: (= (hello nil) "Hello, nil") actual: (not (= "Hello, " "Hello, nil"))false

Logging
Clojure世界:日志管理——clojure.tools.logging
(ns example.core (:use [clojure.tools.logging :only (info error)]))(defn divide [x y] (try (info "dividing" x "by" y) (/ x y) (catch Exception ex (error ex "There was an error in calculation"))))

Storm里面對其進行了封裝, backtype.storm.log
(log-message "test," (pr-str '(1 2 3 4 5)))

pr-str
user=> (def x [1 2 3 4 5]);; Turn that data into a string...user=> (pr-str x)"[1 2 3 4 5]";; ...and turn that string back into data!user=> (read-string (pr-str x))[1 2 3 4 5]

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,908評論 6 541
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,324評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,018評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,675評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,417評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,783評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,779評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,960評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,522評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,267評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,471評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,009評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,698評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,099評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,386評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,204評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,436評論 2 378

推薦閱讀更多精彩內容