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
(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]