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阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容