Clojure

介绍 #

    是jvm上的一个lisp语言变种,比lisp更强调纯函数式编程
    操作符知道自己的特征值(identity value), 如+是0, *是1
    数组是懒惰的,需要时求值。适用于任意层的嵌套。头元素在使用后舍弃
    集合(vector, map, set)都是持久的,使用共享结构,与ruby, java中非持久结构有相似的性能
            # 持久的数据结构中,其它线程对数据的修改对该线程是不可见的
    没有尾递归优化,不常用递归,要用loop.recur

语法 #

    s-expressions
            (max 3 5)
            (+ 1 (* 2 3))
            (def meaning-of-life 42)
            (if (< meaning-of-life 0) "negative" "non-negative")
    (def droids ["Huey" "Dewey" "Louie"])
            (count droids)
            (droids 0)
    (def me {:name "Paul" :age 45 :sex :male})
            (:age me)
    (defn percentage [x p] (* x (/ p 100.0)))
            (percentage 200 10)

并发 #

o-> 原子变量
        # 对一个值进行同步更新
(def my-atom (atom 42))
(deref my-atom)
@my-atom
(swap! my-atom inc)
(swap! my-atom + 2)
(reset! my-atom 0)

(def session (atom {}))
(swap! session assoc :username "paul")

(if (compare-and-set! a old new)
        # 判断原子变量a的值是否是old, 是时赋成new并返回true
new
(recur))

o-> conj 添加新成员
(def players (atom ()))
(defn list-players []
(response (json/encode @players)))
(defn create-player [player-name]
(swap! players conj player-name)
(status (response "") 201))
(defroutes app-routes
(GET "/players" [] (list-players))
(PUT "/players/:player-name" [player-name] (create-player player-name)))
(defn -main [& args]
(run-jetty (site app-routes) {:port 3000}))

o-> cons列表首添加元素
(def listv2 (cons 4 listv1))

o-> validator
        # 值改变之前调用
(def non-negative (atom 0 :validator #(>= % 0)))
(reset! non-negative -1)

o-> 监视器
        # 值改变之后调用 
(def a (atom 0))
(add-watch a :print #(println "Changed from " %3 " to " %4))
(swap! a + 2)
        # !的命名表示函数是事务不安全的

o-> 代理
        # 对一个值进行异步更新。
        # 代理维护的数据与事务数据相同。代理具有事务性,send会在事务成功后生效
        # 方便做内存并发日志系统
(def my-agent (agent 0))
@my-agent
(send my-agent inc)
        # send在值更新之前立即返回,不进行重试。多线程同时调用send, 调用被串行。具有副作用
        # send使用公用线程池,send-off使用一个新线程,send-via使用由参数指定的executor
(send my-agent #((Thread/sleep 2000) (inc %)))
        # 设置延迟时间
(await my-agent)
        # 等待代理执行完成后再继续。await-for函数可以设置超时时间

(def non-negative (agent 1 :validator (fn [new-val] (>= new-val 0))))
        # 代理可以使用校验器和监视器
        # 校验器失败时抛出异常,代理进入失效状态
        # 错误处理模式默认为 :fail, 可以置为:continue
        # 可以设置错误处理函数
(agent-error non-negative)
        # 查看代理是否在失效状态
(restart-agent non-negative 0)
        # 重置失效状态

o-> 引用
        # 只有在事务中才能修改引用的值,对多个值进行同步更新
(def my-ref (ref 0))
@my-ref

(dosync (ref-set my-ref 42))
        # dosync创建一个事务,事务同swap!一样,用重试机制实现
        # clojure的事务有原子性,一致性,隔离性,没有持久性
(dosync (alter my-ref inc))
        # commute替换alter,可以得到不很强的隔离性,用于做优化
(defn transfer [from to amount]
(dosync 
    (alter from - amount)
    (alter to + amount)))

o-> threed
(defn stress-thread [from to iterations amount]
(Thread. #(dotimes [_ iterations] (transfer from to amount))))
(let [t1 (stress-thread checking savings 100 100)
    t2 (stress-thread savings checking 200 100)]
(.start t1)
(.start t2)
(.join t1)
(.join t2))

o-> ensure确保当前返回的值不被其它事务修改
(when (and (= (ensure left) :thinking) (= (ensure right) :thinking))
(ref-set philosopher :eating))

csp #

介绍
        core.async提供了channel和go块
        引入的core.async中部分函数名与clojure核心库函数名冲突

o-> channel
(def c (chan))
(thread (println "Read:" (<!! c) "from c"))
        # thread是core.async提供的辅助宏,将其中代码运行在一个单独的线程上
(>!! c "Hello thread")

用例 #

o->求和
(defn recursive-sum 
""
        # 文档字符串
        ## (require '[philosophers.util :refer :all])
        ## (clojure.repl/doc swap-when!) 来查看文档字符串
[numbers & args])
        # &表示可变参数
        ## (apply f old args) 将args展开,作为附加参数传递给f
(if (empty? numbers)
    0
    (+ (first numbers) (recursive-sum (rest numbers))))

(defn reduce-sum [numbers]
(reduce (fn [acc x] (+ acc x)) 0 numbers))

(defn sum [numbers]
(reduce + numbers))

o->并行
(ns sum.core
(:require [clojure.core.reducers :as r]))

(defn parallel-sum [numbers]
(r/fold + numbers))

(def numbers (into [] (range 0 10000)))
(time (sum numbers))
(time (sum numbers))
        # 预热jim编译器
(time (parallel-sum numbers))

o-> map
(def counts {"apple" 2 "orange" 1})
        (get counts "apple" 0)
        (get counts "banana" 0)
                # 没有时返回设定的默认值0
        (assoc counts "banana" 1)
        (assoc counts "apple" 3)

o-> frequencies
(defn word-frequencies [words]
(reduce
(fn [counts word] (assoc counts word (inc (get counts word 0))))
{} words))

(frequencies ["one" "potato"])
        # 标准库中已提供

o-> partial函数
        # 返回一个被局部代入的函数
(def multiply-by-2 (partial * 2))
(multiply-by-2 3)

o-> 序列
(defn get-words [text] (re-seq #"\w+" text))
(get-words "one tow three four")
(map get-words ["one two three" "four five six"])
(mapcat get-words ["one two three" "four five six"])
        # 平辅数组

o-> iterate
        # 不断将函数应用到初始值,第一次返回值,第二次返回值
(take 10 (iterate inc 0))
(take 10 (iterate (partial + 2) 0))
(take-last 5 (range 0 10000))
        # 头元素使用后舍弃,耗相同的内存

o-> pmap
(pmap #(frequencies (get-words %)) pages)
        # pmap在需要结果时并行计算,仅生成需要的结果,称为半懒惰(semi-lazy)
        # #(...)是读取器宏,来快速创建匿名函数,参数通过%1, %2标识, 只有一个参数时可以是%
        ## (fn [page] (frequencies (get-words page)))与其等价

o-> merge-with
        # 标准库函数
(merge-with f & maps)
        # 将maps中其余map合并到第一个map中,返回合并后的map
        ## 同键名时,多个值从左向右地合并,调用传递的f(val-in-result val-in-latter)
(def merge-counts (partial merge-with +))
(merge-counts {:x 1 :y 2} {:y 1 :z 1})

o-> partition-all
        # 序列分批
(partition-all 4 [1 2 3 4 5 6 7 8 9 10])
        # ((1 2 3 4) (5 6 7 8) (9 10))

o-> reducers包
        # 化简器,不代表函数的结果,代表如何产生结果的描述
        ## 嵌套的函数返回化简器,比返回懒惰序列效率更高
        ## 可以对整个嵌套链的集合操作,可以用fold进行并行化
        # clojure.core中大部分函数都有其对应的化简器版本
(require '[clojure.core.reducers :as r]')
(r/map (partial * 2) [1 2 3 4])
        # 返回一个化简器(reducible)
(reduce conj [] reducible)
        # conj函数第一个参数为一个集合(初始值为[]), 将第二个参数合并到第一个参数中
(into [] reducible)
        # into函数为内置函数,同上

o->协议(类似java中的接口)来定义
(defprotocol CollReduce
        # 化简
(coll-reduce [coll f] [coll f init]))
        # coll相当于this, 支持多态性分派(polymorphic dispatch)
(coll-reduce coll f)

(defn my-reduce
([f coll] (coll-reduce coll f))
([f init coll] (coll-reduce coll f init)))
(my-reduce + [1 2 3 4])
(my-reduce + 10 [1 2 3 4])

(defn make-reducer [reducible transforms]
(reify
    CollReduce
    (coll-reduce [_ f1]
    (coll-reduce reducible (transformf f1) (f1)))
    (coll-reduce [_ f1 init]
    (coll-reduce reducible (transformf f1) init))))
        # 用reify实现一个协议
        # 调用reducible的coll-reduce方法。用transformf对f1进行转换,转换出的函数作为传给coll-reduce方法的一个参数
        # _表示未被使用的函数参数名,可以写成(coll-reduce [this f1])

(defn my-map [mapf reducible]
(make-reducer reducible
    (fn [reducef]
    (fn [acc v]
        (reducef acc (mapf v))))))
        # acc是之前化简结果, v是集合元素。mapf对v进行转换

o-> fold折叠
        # 不能适用于懒惰序列
(defprotocol CollFold
(coll-fold [coll n combinef reducef]))

(defn my-fold
([reducef coll]
    (my-fold reducef reducef coll))
([combinef reducef coll]
    (my-fold 512 combinef reducef coll))
([n combinef reducef coll]
    (coll-fold coll n combinef reducef)))

(defn make-reducer [reducible transformf]
(reify
    CollFold
    (coll-fold [_ n combinef reducef]
    (coll-fold reducible n combinef (transformf reducef)))

    (CollReduce
    (coll-reduce [_ f1]
        (coll-reduce reducible (transformf f1) (f1)))
    (coll-reduce [_ f1 init]
        (coll-reduce reducible (transformf f1) init))))

(def numbers (into [] (take 10000000 (repeatedly #(rand-int 10)))))
(require ['reducers.parallel-frequencies :refer :all'])
(time (frequencies numbers))
(time (parallel-frequencies numbers))

o-> doall强迫懒惰序列对全部元素求值
(reduce + (doall (map (partial * 2) (range 10000))))

o-> future
        # 单独线程中执行一段代码
        # 典型场景是异步通信
(def sum (future (+ 1 2 3 4 5)))
sum
        # 返回一个future对象
(deref sum)
@sum
        # 运行
(let [a (future (+ 1 2))
    b (future (+ 3 4))]
(+ @a @b))
        # let给a赋值,阻塞当前线程直到被求值
        # 外层加法将一直阻塞,直到所有代表的值被求值

o-> promise
        # 创建promise对象后,代码并不会像future一样立即执行,等待deliver赋值后执行
(def meaning-of-life (promise))
(future (println "The meaning of life is:" @meaning-of-life))
(deliver meaning-of-life 42)

o-> Compojure库的服务器
(def snippets (repeatedly promise))
(defn accept-snippet [n test]
(deliver (nth snippets n) test))
(future
(doseq [snippet (map deref snippets)]
    (println snippet)))

(defroutes app-routes
(PUT "/snippet/:n" [n :as {:keys [body]}]
    (accept-snippet (edn/read-string n) (slurp body))
    (response "OK")))
(defn -main [& args]
(run-jetty (site app-routes) {:port 3000}))

o-> re-seq正则
(defn sentence-split [text]
(map trim (re-seq #"[^\.!\?:;]+[\.!\?:;]*" text)))
        # trim是内置函数
(defn is-sentence? [text]
(re-matches #"^.*[\.!\?:;]$" text))

o-> reductions
        # 同reduce, 返回中间值构成的序列
(reductions + [1 2 3 4])
        # (1 3 6 10)

o-> clj-http库
(def translator "http://localhost:3001/translate")
(defn translate [text]
(future
    (:body (client/post translator {:body text}))))

o-> delay在解引用前不求值
(def translations
(delay
    (map translate (strings->sentences (map deref snippets)))))

o-> 系统时间
(defn now []
(System/currentTimeMillis))

o-> Schejulure库
(def session-sweeper
(schedule {:min (range 0 60 5)} sweep-sessions))
        # 定期调用

o-> Useful库
(defn expired? [session]
(< @(:last-referenced session) (session-expiry-time)))
(defn sweep-sessions []
(swap! sessions #(remove-vals % expired?)))
        # 删除元素

o-> Loop/Recur
(defn swap-when! [a pred f & args]
(loop []
    (let [old @a]
    (if (pred old)
        (let [new (apply f old args)]
        (if (compare-and-set! a old new)
            new
            (recur)))
        nil))))

工具 #

clojureScript
        # 编译到js