Clojure常用模块

    xiaoxiao2024-05-19  115

    Base

     

    ->, (-> x form & more)

    http://clojuredocs.org/clojure_core/clojure.core/->

    线性化嵌套, 使其更具有可读性,  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/->>

    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

     

    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 function user=> (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])) 1 2 3 (nil nil nil) (dorun (map println [1 2 3])) 1 2 3 nil

    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))) 1 2 3 2 4 6 3 6 9 nil

     

    并发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调用

    先看下最常用的对应表,

     

    (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 sourceTo speed up application startupTo generate named classes for use by JavaTo 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*)) 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))))

     

    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) ;;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

    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] 本文章摘自博客园,原文发布日期:2013-06-04 相关资源:敏捷开发V1.0.pptx
    最新回复(0)