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, (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调用
先看下最常用的对应表,

22[模块]Clojure常用模块_第1张图片
image

22[模块]Clojure常用模块_第2张图片
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]

你可能感兴趣的:(22[模块]Clojure常用模块)