函数式编程之Clojure(聚会讲稿)

w3xd

贡献于2015-01-11

字数:0 关键词: Clojure开发 Clojure

郝林(@特价萝卜) 来自搜狐技术部 这就是Clojure  Clojure是 • 一种Lisp方言(最初只基于JVM构建,现在也有CLR和JS的版本) • 开源语言(使用Eclipse Public License v 1.0协议) • 动态类型语言(标识类型是可选操作) • 函数式语言(但提供了安全的可变状态操作方法) • 作者: Rich Hickey • 2007年10月第一次发布 • 官方网站: http://www.clojure.org/ • 中文用户组网站:http://cnlojure.org/ http://wiki.clojure.cn http://ask.clojure.cn/ http://blog.clojure.cn/ 这就是Clojure  Clojure拥有 • 可以在JDK 5.0(及以上)上构建和运行的代码 • 一个稳定、高效和跨操作系统的运行平台(JVM) • 可以无缝使用丰富的Java类库和资源 • 可以为其他Java代码提供API • 极少的语法,非常小的核心,高扩展性 • 代码即数据(code-as-data) & 句法抽象 • 不可变状态 & 高阶函数 • 强大的宏! 这就是Clojure  Clojure表达式 在Clojure中,任何语句都是表达式,表达式的计算结果为一个值。 • “ (”和“)”以及被它们括起来的内容被叫做列表(List),列表中的 第一个位置被叫做函数位(function position)。调用列表会使它被求职 并将值返回给调用方。 • 符号(Symbols)在当前范围中被评估成一个命名值,这个当前范围 可能是一个函数、一个Java类、一个宏或一个special form。 • 所有其他的表达式都被评估为一个它们所表示的字面值。 这就是Clojure  Clojure基本语法 在Clojure中,所有的表达式调用只遵循一个规则:列表中的第一个值是 操作符,其余的都是给这个操作符的参数。 这就是Clojure  Clojure数据类型 数据类型 例子 String "Clojure" Boolean true, false Nil nil Character \a, \u00ff, \o41, \tab Keyword :tag, :doc Symbol (defn sum [& numbers] (apply + numbers))中的sum等 Regular expression (re-seq #"(\d+)-(\d+)" "1-3") ;;=(["1-3" "1" "3"]) Number • 42, 0xff, 2r111, 040 ;; long • 3.14, 6.0221415e23 ;; double • 42N ;; clojure.lang.BigInt • 0.01M ;; java.math.BigDecimal • 22/7 ;; clojure.lang.Ratio 这就是Clojure  Clojure集合数据结构 所有集合数据结构都可任意嵌套,组成更复杂的数据结构。 集合数据结构 例子 说明 List '(a b :name 12.5), (list 1 2 3) 链表 Vector ['a 'b :name 12.5], (vec (range 3)) 类似数组,索引访问 Map {:name "Clojure" :age 5} key/value结构 Set #{1 2 3} 集合,消除重复 ‘({:Author {:name "Hao Lin", :dept "Tech"}} "A member of MySohu team." ["Java" "Clojure" "Go"]) ;;= ({:Author {:name "Hao Lin", :dept "Tech"}} "A member of MySohu team." ["Java" "Clojure" "Go"]) 这就是Clojure  Clojure代码 • 列表、表达式(form)与求值: (op arg1 arg2 arg3) 其中op可以是:special form、function以及macro。 (println "Hello," "Clojure" "!") ;;= Hello, Clojure ! ;;= nil 这就是Clojure  Clojure代码 • 定义一个值: (def name value) 用def来定义值。 除了把“=”替换为“(”和“)”,貌似没什么特殊。 (def number 123) (println “The number:” number) ;;= The number: 123 ;;= nil 这就是Clojure  Clojure代码 • 定义一个函数: (defn name ―document/explain‖ [arg1 arg2 arg3] ) 用defn来定义函数。 defn后面依次跟函数名、函数说明(可选)、参数列表 和函数体。 这里可以嵌套任意的表达式调用。 这就是Clojure  Clojure代码 • 定义一个函数: (defn count-word "Count every word of parameter 's'." [s] (reduce #(assoc %1 %2 (inc (%1 %2 0))) {} (re-seq #"\w+" s))) (count-word "Clojure is a a dynamic programming language") ;;= {"language" 1, "programming" 1, "dynamic" 1, "a" 2, "is" 1, "Clojure" 1} 这就是Clojure  Clojure代码 • 定义一个函数: ((fn [s] (reduce #(assoc %1 %2 (inc (%1 %2 0))) {} (re-seq #"\w+" s))) "Clojure is a a dynamic programming language") ;;= {"language" 1, "programming" 1, "dynamic" 1, "a" 2, "is" 1, "Clojure" 1} (#(reduce (fn [m k](assoc m k (inc (m k 0)))) {} (re-seq #"\w+" %)) "Clojure is a a dynamic programming language") ;;= {"language" 1, "programming" 1, "dynamic" 1, "a" 2, "is" 1, "Clojure" 1} 这就是Clojure  Clojure代码 • special form: special form是并不遵从一般form的组成规则和执行规则 的特殊form。它是Clojure中最基本的计算操作,是构建其他 Clojure代码的基础。 special form包括: def、if、do、let、quote、var、fn、 loop、recur、new等等。 这就是Clojure  Clojure代码 • special form: (let [n 10] (if (> n 0) (cons 4 '(5 6)) '(1 2 3))) ;;= (4 5 6) (loop [n 5 result 1] (cond (<= n 0) result :else (recur (- n 1) (* n result)))) ;;= 120 Clojure与函数式编程 Clojure is a functional programming language. Clojure与函数式编程  函数式编程(Functional programming) • 一种编程范式 • 程序运算即为数学上的函数计算 • 以 λ 演算(lambda calculus)为基础 • 函数为first-class,可以很方便的运用闭包创造出高阶函数 • 避免状态、变量和副作用,保证引用透明性 • 懒惰计算(lazy evaluation)和闭包(closure) Clojure与函数式编程  函数式编程 - 不可变的数据 (def a '(1 2 3)) (def b (cons 0 a)) (do (println "a is" a) (println "b is" b)) ;;= a is (1 2 3) ;;= b is (0 1 2 3) ;;= nil Clojure与函数式编程  函数式编程 - 不可变的数据 (def a {:a 5, :b 6, :c 7, :d 8}) (def b (assoc a :c 0)) (def c (dissoc a :d)) (do (println "a is" a) (println "b is" b) (println "c is" c)) ;;= a is {:a 5, :c 7, :b 6, :d 8} ;;= b is {:a 5, :c 0, :b 6, :d 8} ;;= c is {:a 5, :c 7, :b 6} ;;= nil Clojure与函数式编程  函数式编程 - 函数是一级类型 (defn my-func1 [d f] (f d)) (my-func1 "It's first-class!" println) ;;= It's first-class! ;;= nil (defn func-a [s] (str "Func A: " s)) (defn func-b [s] (str "Func B: " s)) (defn my-func2 [n] (cond (> n 0) func-a :else func-b)) (println ((my-func2 0) "my-first-class")) ;;= Func B: my-first-class ;;= nil Clojure与函数式编程  函数式编程 – 懒惰计算 (println (take 10 (iterate inc 1))) ;;= (1 2 3 4 5 6 7 8 9 10) ;;= nil (defn lazy-seq-fibo ([] (concat [0 1] (lazy-seq-fibo 0 1))) ([a b] (let [n (+ a b)] (lazy-seq (cons n (lazy-seq-fibo b n)))))) (println (take 10 (lazy-seq-fibo))) ;;= (0 1 1 2 3 5 8 13 21 34) ;;= nil Clojure与函数式编程  函数式编程 – 闭包 • 闭包这个词源自于通过“捕获”自由变量的绑定对函数文 本执行的“关闭”行动。 ;; The original function of closure (defn plus-n [x] (fn [y] (+ x y))) ;; Close it! (def plus-5 (plus-n 5)) ;; use closure (plus-5 3) ;;= 8 Clojure与函数式编程  为什么不用函数式思想来编程呢? • 处理数据?用管道的方式会更加简洁 • 简单既美,目标导向 • 要亲自管理可变状态?敬而远之吧 • 更自然的使用“组合”来解耦代码 Clojure与函数式编程 • 处理数据?用管道的方式会更加简洁 (遍历列表,提取每个奇数并都乘以2,最后求和并打印) 想象一下,如果用Java写的话需要多少行代码?需要多少次 循环?需要声明多少个中间变量? (println (reduce + (map #(* 2 %) (filter odd? (range 1 20))))) ;;= 200 ;;= nil Clojure与函数式编程 • 简单既美,目标导向 (找出 1 到 100 中能被 3 整除的数,然后组成序列并打印) 代码会向文字说明那样简单和直观。其他细节?都交给 Clojure去做吧! (println (for [n (range 1 101) :when (= 0 (rem n 3))] n)) ;;= (3 6 9 12 15 18 21 24 27 30 33 36 39 42 45 48 51 54 57 60 63 66 69 72 75 78 81 84 87 90 93 96 99) ;;= nil Clojure与函数式编程 • 要亲自管理可变状态?敬而远之吧 (一个简单的计数程序) 这里的可变状态是并发安全的!所有的可变状态也都会是! (def counter (let [tick (atom 0)] #(swap! tick inc))) (println (take 10 (repeatedly counter))) ;;= (1 2 3 4 5 6 7 8 9 10) ;;= nil Clojure与函数式编程 • 更自然的使用“组合”来解耦代码(1) (一个“递进编写”的日志记录程序) print-logger函数把日志打印到屏幕上。binding是一个宏,用 于在局部范围内将指定值绑定到外部命名上。 (defn print-logger [writer] #(binding [*out* writer] (println %))) ((print-logger *out*) "hello") ;;= hello ;;= nil Clojure与函数式编程 • 更自然的使用“组合”来解耦代码(2) file-logger函数调用print-logger函数把日志记录到指定文 件中。另外注意file-logger函数的返回值和调用它的方式。 (defn file-logger [file] #(with-open [f (clojure.java.io/writer file :append true)] ((print-logger f) %))) ((file-logger "messages.log") "hello, log file.") ;;= nil $ cat messages.log hello, log file. Clojure与函数式编程 • 更自然的使用“组合”来解耦代码(3) multi-logger函数实现了多输出的(屏幕和文件)的日志 记录操作。messages.log文件会被附加上一行内容: “hello again” (defn multi-logger [& logger-fns] #(doseq [f logger-fns] (f %))) ((multi-logger (print-logger *out*) (file-logger "messages.log")) "hello again") ;;= hello again ;;= nil Clojure与函数式编程 • 更自然的使用“组合”来解耦代码(4) 好吧,还需要时间戳。timestamped-logger函数实现了它。 实现操作的代码被逐层包装(函数)和返回,最后被调用。 (defn timestamped-logger [logger] #(logger (format "[%1$tY-%1$tm-%1$te %1$tH:%1$tM:%1$tS] %2$s" (java.util.Date.) %))) ((timestamped-logger (multi-logger (print-logger *out*) (file-logger "messages.log"))) "Hello, timestamped logger~") ;;= [2012-10-19 08:22:47] Hello, timestamped logger~ ;;= nil 再探Clojure 现在,让我们讨论得更深入一些… 再探Clojure  quote & eval • 任何form都可以被quote修饰,包括数据结构。被quote修 饰的form都会被延迟评估。 • 所有的评估语义都被封装到了一个叫eval的函数中了。 eval函数会立即评估参数form。 'x ;;= x '(+ x x) ;;= (+ x x) (list? '(+ x x)) ;;= true (def x 10) (eval '(+ x x)) ;;= 20 (eval (read-string "(+ x x)")) ;;= 20 再探Clojure  let & binding (1) • let可在任何地方使用以绑定本地值,特别是fn(以及其他 创建/定义函数的form,比如defn)使用let绑定函数参数以 作为其函数作用域中的本地值。 (defn hypot [x y] (let [x2 (* x x) y2 (* y y)] (Math/sqrt (+ x2 y2)))) (hypot 2 3) ;;= 3.605551275463989 再探Clojure  let & binding (2) • let是解构集合的工具。因为解构功能由let提供,所以它能 被用在任何隐含使用let的form中。 (def val [42 "foo" 99.2 [5 12]]) (do (let [[x y z] val] (println (+ x z))) (let [[x _ _ [y z]] val] (println (+ x y z))) (let [[x & rest] val] (println rest)) (let [[x _ z :as orig] val] (println (conj orig (+ x z))))) ;;= 141.2 ;;= 59 ;;= (foo 99.2 [5 12]) ;;= [42 foo 99.2 [5 12] 141.2] ;;= nil 再探Clojure  let & binding (3) • 更多的例子: (def m {:a 5, :b 6, :c [7 8 9], :d {:e 10, :f 11}, "foo" 88, 42 false}) (do (let [{a :a b :b} m] (println (+ a b))) (let [{f "foo"} m] (println (+ f 12))) (let [{v 42} m] (println (if v 1 0))) (let [{{e :e} :d} m] (println(* 2 e))) (let [{[x _ y] :c} m] (println (+ x y)))) ;;= 11 ;;= 100 ;;= 0 ;;= 20 ;;= 16 ;;= nil 再探Clojure  let & binding (4) • binding是一个宏,它可以将外部命名与新值绑定,这个新 绑定的影响范围为同一线程之内的从此binding调用开始的 调用链。 (def foo 10) (defn print-foo [] (println foo)) (let [foo "let foo"] (print-foo)) ;;= 10 ;;= nil (def ^:dynamic foo 10) (defn print-foo [] (println foo)) (binding [foo "bound foo"] (print-foo)) ;;= bound foo ;;= nil 再探Clojure  Special form – loop & recur • recur可以在不消耗堆栈空间的情况下将控制转回循环的起 始位置,这里的起始位置是指loop或函数的定义位置。 (loop [x 5] (if (neg? x) x (recur (dec x)))) ;;=-1 (defn count-down [x v] (if (zero? x) (conj v :blastoff!) (do (recur (dec x) (conj v x))))) (count-down 3 []) ;;= [3 2 1 :blastoff!] 再探Clojure  Special form – . & new • 所有的与Java的互操作功能——实例化、静态和实例方法 调用和字段访问——都由 “.”和“new”这两个special form来提供。 再探Clojure  Macro macro允许使用者控制Clojure编译器,并在一定范围内对语 言的规则和句法进行微妙的调整。Clojure的宏能够让使用者 定义他们想要的计算操作集合,并使其能够像语言内建计算 操作那样去被使用。宏,是我们扩展语言的一把利器。 。 常用的macro有:when、cond、lazy-seq、delay、if-not、 and、or、..、->、->>、binding等等。 再探Clojure  Macro语法 (1) 语法 说明 示例 ` 语法引用,用于在宏中引用表 达式,作用是延迟表达式的评 估。 `(println "A") ;;= (clojure.core/println "A") ~ 反语法引用,用在语法引用 (`)范围内,作用是标识其 中不需要延迟评估的子表达式。 `(println ~(str [1])) ;;= (clojure.core/println "[1]") ~@ 反语法引用拼接,在语法引用 (`)范围内,对多个子表达 式进行反语法应用。 (let [defs ‗((def x 1) (def y 2))] `(do ~@defs)) ;;= (do (def x 1) (def y 2)) 再探Clojure  Macro语法 (2) 语法 说明 示例 ~‘ 在语法引用(`) 范围内,对本 地绑定名进行 直译。 `(let [~'name "ClojureCN"] (println ~'name)) ;;= (clojure.core/let [name ―ClojureCN‖] ;;= (clojure.core/println name)) ‗~ 在语法引用(`) 范围内,代表 绑定名称本身。 (defmacro show [v] `(str '~v ":" ~v)) (show "abc") ;;= "abc:abc" (let [name "abc"] (show name)) ;;= "name:abc" 再探Clojure  Macro示例 (defmacro keyword-map ([] {}) ([coll] `(let [~'e ~coll] (reduce conj (map #(assoc {} (keyword (str %)) %) (if (coll? ~'e) ~'e (vector ~'e)))))) ([coll & next] `(conj (keyword-map ~coll) (keyword-map ~@next)))) (keyword-map 123 "abc" [4 5 6] '("x" "y" "z") #{"i" "j" "k"}) ;;= {:z "z", :y "y", :x "x", :6 6, :4 4, :5 5, :abc "abc", :123 123, :k "k", :j "j", :i "i"} 再探Clojure  Sequences (1) 序列抽象定义了一套用来获得和遍历一些数据值上的序列视 图的方法。这些数据要么是一个写集合,要么是一些计算产 生的连续的结果。序列常被叫做“seqs”,它提供了一些除 了基本集合抽象之外几个方法: • seq——可以根据参数生成序列。 • first、rest和next提供定位和遍历序列中值的方法。 • lazy-seq根据表达式的评估结果生成懒加载序列。大多 数seq函数都是lazy的,返回lazy seq(s)。 再探Clojure  Sequences (2) 可序列化的数据类型(即供seq函数作为参数的)包括: • 所有的Clojure集合数据类型。 • 所有的Java集合数据类型。 • 所有的Java映射(Map)。 • 所有的Java字符序列,包括String类型。 • 所有实现了Java的Iterable接口的类型。 • 数组 • nil • 任何实现了Clojure的clojure.lang.Seqable接口的类型。 再探Clojure  Sequences (3) • 操作seq的一些基本方法: (defn show-seq [s] (vector (first s) (second s) (rest s) (next s) (nthrest s 4) (take 4 s) (take-last 4 s) (butlast s) (drop 2 s) (drop-last 2 s))) (show-seq "Clojure") ;;= [\C \l (\l \o \j \u \r \e) (\l \o \j \u \r \e) (\u \r \e) (\C \l \o \j) (\j \u \r \e) (\C \l \o \j \u \r) (\o \j \u \r \e) (\C \l \o \j \u)] 再探Clojure  Sequences (4) • 用seq模仿堆栈(Stack): (conj '() 1 2 3) (peek '(3 2 1)) (pop '(3 2 1)) (conj [] 1 2 3) (peek [1 2 3]) (pop [1 2 3]) ;;= (3 2 1) ;;= 3 ;;= (2 1) ;;= [1 2 3] ;;= 3 ;;= [1 2] 再探Clojure  Sequences (5) • 关于set: (def lang #{"java" "python" "golang" "clojure"}) (def drink #{"java" "chai" "pop"}) (require '[clojure.set :as set]) (set/union lang drink) ;;= #{"java" "clojure" "chai" "pop" "golang" "python"} (set/difference lang drink) ;;= #{"clojure" "golang" "python"} (set/intersection lang drink) ;;= #{"java"} (set/select #(= 6 (.length %)) lang) ;;= #{"golang" "python"} 再探Clojure  Sequences (6) • 关于set: (def lang-times #{{:lang "lisp",:times 1950}, {:lang "java", :times 1990}, {:lang "clojure", :times 2000}, {:lang "golang", :times 2010}}) (def lang-paradigms #{{:lang "lisp", :paradigms ["fp"]}, {:lang "java", :paradigms ["oop"]}, {:lang "clojure", :paradigms ["fp" "oop"]}, {:lang "golang", :paradigms ["multi"]}}) (set/project lang-times [:lang]) ;;= #{{:lang "golang"} {:lang "lisp"} {:lang "java"} {:lang "clojure"}} 再探Clojure  Sequences (7) • 关于set: (set/join lang-times lang-paradigms) ;;= #{{:times 1990, :lang "java", :paradigms ["oop"]} {:times 2000, :lang "clojure", :paradigms ["fp" "oop"]} {:times 2010, :lang "golang", :paradigms ["multi"]} {:times 1950, :lang "lisp", :paradigms ["fp"]}} (set/project (set/join (set/select #(>= (:times %) 2000) lang-times) lang-paradigms) [:lang]) ;;= #{{:lang "golang"} {:lang "clojure"}} 再探Clojure  Sequences (8) • 关于map: (def lang-times {:lisp "195x", :java "199x" :clojure "200x"}) (keys lang-times) ;;= (:clojure :java :lisp) (vals lang-times) ;;= ("200x" "199x" "195x") (get lang-times :clojure) ;; or (lang-times :clojure) ;;= "200x” (contains? lang-times :golang) ;;= false (get lang-times :golang :not-found!) ;;= :not-found! 再探Clojure  Sequences (9) • 关于map: (assoc lang-times :golang "201x") ;;= {:clojure "200x", :golang "201x", :java "199x", :lisp "195x"} (dissoc lang-times :java) ;;= {:clojure "200x", :lisp "195x"} (select-keys lang-times [:clojure :java]) ;;= {:java "199x", :clojure "200x"} (merge lang-times {:golang "201x"}) ;;= {:clojure "200x", :golang "201x", :java "199x", :lisp "195x"} 再探Clojure  Sequences (10) • 关于map: (merge-with concat {:lisp ["common lisp"], :c ["c"]} {:lisp ["scheme"], :c ["c++" "java"]} {:lisp ["clojure"], :c ["golang"]}) ;;= {:c ("c" "c++" "java" "golang"), :lisp ("common lisp" "scheme" "clojure")} 再探Clojure  STM(Software Transactional Memory) (1) Java的并发模型: • 直接引用可变状态(耗费大量精力来保证其一致性) • 采用锁来保护(悲观策略) • 死锁的风险 • 无法进行组合 • 出错后回滚困难 再探Clojure  STM(Software Transactional Memory) (2) Clojure的STM使用了一种叫做多版本并发控制(MVCC)的 技术。这一技术也在被一些主流数据库使用。它保证如下属 性: • 更新是原子的(Atomicity) • 更新是一致的(Consistency) • 更新是隔离的(Isolation) 数据库的事务还可以保证更新是持久的(Durability)。因为 Clojure的事务是内存事务,所以并不能保证更新的持久性。 再探Clojure  STM(Software Transactional Memory) (3) 再探Clojure  STM(Software Transactional Memory) (4) Clojure STM的四种操作模式 • 协作/独立:状态是否与其他状态共同作用 • 同步/异步:状态的更新是同步还是异步 name Coordinated/Independent Synchronous/Asynchronous Ref Coordinated Sync Atomic Independent Sync Agent Independent Async Vars ThreadLocal Sync 再探Clojure  STM(Software Transactional Memory) (5) Ref会为一个不可变的对象创建一个可变的引用。 (def current-track (ref "CNClojure 2012")) (deref current-track) ;;= "CNClojure 2012“ @current-track "CNClojure 2012“ (ref-set current-track "CNClojure 2012 (2)") ;;= IllegalStateException No transaction running clojure.lang.LockingTransaction.getEx (LockingTransaction.java:208) 再探Clojure  STM(Software Transactional Memory) (6) Ref会为一个不可变的对象创建一个可变的引用。 (dosync (ref-set current-track "CNClojure 2012 (2)")) ;;= “CNClojure 2012 (2)” (def sponsor (ref "Sohu")) (dosync (ref-set current-track "CNClojure 2012 (2) Meeting") (ref-set sponsor "Sohu, AVOS")) ;;= "Sohu, AVOS" 再探Clojure  STM(Software Transactional Memory) (7) Clojure的alter函数会在事务中将一个更新函数应用到一个被 引用的对象上,并返回这个引用的新值。它比ref-set可读性 更强。 (defrecord Message [sender text]) ;;= user.Message (user.Message. "hyper-carrot" "Hello") ;;= {:sender "hyper-carrot", :text "Hello"} (def messages (ref())) 再探Clojure  STM(Software Transactional Memory) (8) (defn add-message [msg] (dosync (alter messages conj msg))) (defn another-add-message [msg] (dosync (ref-set messages (cons msg @messages)))) (add-message (user.Message. "User 1" "Hello")) ;;= ({:sender "User 1", :text "Hello"}) (another-add-message (user.Message. "User 2" "Bye")) ;;= ({:sender "User 2", :text "Bye"} {:sender "User 1", :text "Hello"}) 再探Clojure  STM(Software Transactional Memory) (9) Clojure的commute函数与alter函数类似,但在并发的执行更 新操作时其执行顺序是不确定的。在更新失败时,事务并不 会被重试,而仅仅会以无序的方式重新运行commute函数。 (def counter (ref 0)) (defn commute-inc! [counter] (dosync (Thread/sleep 100) (commute counter inc))) (defn alter-inc! [counter] (dosync (Thread/sleep 100) (alter counter inc))) 再探Clojure  STM(Software Transactional Memory) (10) (defn bombard-counter! [n f counter] (apply pcalls (repeat n #(f counter)))) (dosync (ref-set counter 0)) (time (doall (bombard-counter! 20 alter-inc! counter))) ;;= "Elapsed time: 2006.892743 msecs" ;;= (1 2 7 4 5 8 13 6 3 9 12 11 10 17 14 15 16 18 20 19) (dosync (ref-set counter 0)) (time (doall (bombard-counter! 20 commute-inc! counter))) ;;= "Elapsed time: 305.601967 msecs" ;;= (1 1 4 6 7 1 4 8 8 12 11 11 12 8 15 15 17 20 18 19) 再探Clojure  STM(Software Transactional Memory) (11) Atom是一种比ref更轻量级的机制。多个ref更新操作能够在一 个事务内被协调的执行,而atom允许非协调的单一值的更新 操作。 (def current-track (atom {:lang "Java" :country "CN"})) (reset! current-track {:lang "Golang" :country "CN"}) ;;= {:country "CN", :lang "Golang"} (swap! current-track assoc :lang "Clojure") ;;= {:country "CN", :lang "Clojure"} (deref current-track) ;;= {:country "CN", :lang "Clojure"} 再探Clojure  STM(Software Transactional Memory) (12) memoize是一个非常有用的函数,它可以缓存一个输入和输 出的映射。这是一个用空间换时间的典型案例。memoize函 数中的内部存储是用atom实现。 (defn ^:dynamic slow-double [n] (Thread/sleep 100) (* n 2)) (defn calls-slow-double [] (map slow-double [1 2 1 2 1 2])) (time (dorun (calls-slow-double))) ;;= "Elapsed time: 600.349527 msecs" ;;= nil 再探Clojure  STM(Software Transactional Memory) (13) (defn demo-memoize [] (time (dorun (binding [slow-double (memoize slow-double)] (calls-slow-double))))) (demo-memoize) ;;= "Elapsed time: 200.447573 msecs" ;;= nil 再探Clojure  STM(Software Transactional Memory) (14) Agent适合那些之间几乎无依赖的任务,因为它是异步的。 send函数被调用后会立即返回,更新操作会稍后在另一个线 程被执行。 (def counter (agent 0 :validator number?)) (send counter inc) ;;= # (await (send counter inc)) ;;= nil (time (await-for 1 (send counter inc))) ;;= "Elapsed time: 1.320692 msecs" ;;= true 再探Clojure  STM(Software Transactional Memory) (15) (time (await-for 1 (send counter inc))) ;;= "Elapsed time: 1.320692 msecs" ;;= true (send counter inc) ;;= # (send counter (fn [_] "Oops!")) ;;= # (send counter inc) ;;= IllegalStateException Invalid reference state clojure.lang.ARef.validate (ARef.java:33) 再探Clojure  STM(Software Transactional Memory) (16) (agent-errors counter) ;;= (#) (clear-agent-errors counter) ;;= 4 (send counter inc) ;;= # 再探Clojure  STM(Software Transactional Memory) (17) Ref、Atom和Agent的更新模型: 再探Clojure  STM(Software Transactional Memory) (18) Var是用defn或def定义,并用^:dynamic修饰的。它可以用 binding在本地线程重新绑定为其他值。 (def ^:dynamic number 10) (defn print-number [] (println number)) (let [number "let number"] (print-number)) ;;= 10 ;;= nil (binding [number "let number"] (print-number)) ;;= let number ;;= nil Clojure周边  Clojure开发工具 • 构建工具——Leiningen,兼容Maven仓库。 • 入门级的IDE——Clooj,集成了项目浏览器、支持语法高 亮Clojure源码文件查看器、输出查看器和REPL。 • 更高级的IDE——推荐 IDEA + La Clojure插件。 • 极客们的IDE——Emacs + … (http://dev.clojure.org/display/doc/Getting+Started+with +Emacs) •…… Clojure周边  Clojure相关网站 • 官网:http://clojure.org • 文档站点:http://clojuredocs.org • 题库站点:http://www.4clojure.com • Clojure构件仓库: https://clojars.org • 中文用户组:http://cnlojure.org http://wiki.clojure.cn http://ask.clojure.cn/ http://blog.clojure.cn/ Clojure周边  Clojure书籍 • Programming Clojure, Second Edition (易入门,基于 Clojure 1.3) • Clojure Programming(O‘Reilly出品,基于Clojure 1.3) • Clojure in Action(实践手册) • The Joy of Clojure(比较深入) • Clojure – Functional Programming for the JVM(易入门, 有中文版) This is only the beginning! Q & A

下载文档,方便阅读与编辑

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 8 金币 [ 分享文档获得金币 ] 1 人已下载

下载文档

相关文档