http://qiujj.com/static/clojure-handbook.html
http://clojure.github.io/clojure/
->, (-> 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) nil user> (defnk f [:b 43] (inc b)) #'user/f user> (f) 44 user> (f :b 100) 101 user=> (defnk with-positional [foo :b 43] (+ foo (inc b))) #'user/with-positional user=> (with-positional 5 :b 1) 7
'(a b :name 12.5) ;; list
['a 'b :name 12.5] ;; vector
{:name "Chas" :age 31} ;; map
#{1 2 3} ;; set
(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
(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}, 删除
(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
(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 function user=> (merge-with + {:a 1 :b 2} {:a 9 :b 98 :c 0}) {:c 0, :a 10, :b 100}
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都用于force lazy-seq, 区别在于
doall会hold head, 并返回整个seq, 所以过程中seq保存在memory中, 注意outofmemory
dorun不会hold head, 遍历run, 最终返回nil
(doall (map println [1 2 3])) 1 2 3 (nil nil nil) (dorun (map println [1 2 3])) 1 2 3 nil
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))) 1 2 3 2 4 6 3 6 9 nil
(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))
(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
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))
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*))
68
user=> (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))))
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
两种形式,
deftest
其实就是创建函数, 象普通函数一样去调用定义的test fn
(deftest test-foo (is (= 1 2))) (test-foo) ;;nil, pass没有输出 (deftest test-foo (is (= 1 2))) (test-foo) ;;fail FAIL 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
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]