JVM上的实用Lisp方言:Clojure

6,991 views
6,750 views

Published on

介绍Clojure语言,主要内容包括:
1. Lisp的核心思想
2. Clojure语言特性
3. 函数式编程
4. Clojure并发机制

Published in: Technology
2 Comments
7 Likes
Statistics
Notes
No Downloads
Views
Total views
6,991
On SlideShare
0
From Embeds
0
Number of Embeds
6
Actions
Shares
0
Downloads
83
Comments
2
Likes
7
Embeds 0
No embeds

No notes for slide

JVM上的实用Lisp方言:Clojure

  1. 1. . JVM 上的实用 Lisp 方言:Clojure Jerry Peng 2012-12-18 1/ Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 1/90.
  2. 2. . Outline 今天的主题 Clojure 简介 揭开 Lisp 的神秘面纱 Clojure 语言特性一览 函数式编程 并发支持 2/ Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 2/90.
  3. 3. . Topic 今天的主题 Clojure 简介 揭开 Lisp 的神秘面纱 Clojure 语言特性一览 函数式编程 并发支持 3/ Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 3/90.
  4. 4. . 思维方式的引导 • Clojure 语言的简要介绍 ◦ Lisp 的特别之处 • 一窥函数式编程的奇妙 • 重在打开一扇思维方式的门 ◦ 私下的学习和实践最重要 4/ Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 4/90.
  5. 5. . Topic 今天的主题 Clojure 简介 揭开 Lisp 的神秘面纱 Clojure 语言特性一览 函数式编程 并发支持 5/ Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 5/90.
  6. 6. . About Clojure • Clojure 发音和 Closure 一样 • 作者是 Rich Hickey • 最新稳定版本是 1.4 ◦ 1.5RC1 也已经 release 了 6/ Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 6/90.
  7. 7. . About Rich Hickey • 十分有想法的一个人 • 强烈推荐看看他关于 Time、Identity 和 Value 的那场演讲 • E2E 上和 Brian Beckman 的讨论 7/ Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 7/90.
  8. 8. . Hello World (defn hello [name] (println "Hello," name)) (hello "John") ; ==> Hello, John 8/ Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 8/90.
  9. 9. . Smarter Hello World (defn say-hello [& names] (println "Hello to" (apply str (concat (interpose ", " (drop-last names)) [" and " (last names)])))) (say-hello "John" "Peter" "Brad" "Jerry") ; ==> Hello to John, Peter, Brad and Jerry 9/ Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 9/90.
  10. 10. . Clojure 语言简介 • Clojure 是一种 Lisp 方言 • Clojure 是函数式编程语言 • Clojure 支持强大的并发机制 • 强大的 REPL 支持(Read-Evaluation-Print-Loop) ◦ 极其高效、舒适的开发体验 10 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 10/90.
  11. 11. . Clojure 是一种 Lisp 方言 • 100% 的 Lisp ◦ REPL 支持 ◦ 强大的宏 • 更现代 ◦ 支持 vector 和 map 的字面量 ◦ 括号尽可能少 • 平台更强大——JVM/CLR ◦ 性能强大、工业标准 ◦ 高质量的库支持 ◦ 无缝的 Java 互操作 ◦ ClojureScript,Clojure-Py 11 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 11/90.
  12. 12. . Clojure 是函数式编程语言 • 强调不可变性(Immutability) • 性能优秀的 Persistent Data Structure ◦ 做到 immutable 的同时兼顾性能 • 强大的 sequence 支持 ◦ 类似 Python 的 iterator,但更强大 • Laziness ◦ Lazy sequences ◦ 大量的基本操作都是 lazy 的 ◦ 自由使用函数式编程风格又无需担心性能 12 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 12/90.
  13. 13. . Clojure 对并发有良好的支持 • Immutability 本身就对并发友好 ◦ 可变状态是并发的天敌 • 简单的原子操作支持(atoms) • 软件事务内存(STM,refs) • 异步编程(agents) 13 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 13/90.
  14. 14. . Topic 今天的主题 Clojure 简介 揭开 Lisp 的神秘面纱 Clojure 语言特性一览 函数式编程 并发支持 14 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 14/90.
  15. 15. . 大牛们怎么说 Lisp 的 . Eric Raymond . “Lisp is worth learning for the profound enlightenment experience you will have when you finally get it; that experience will make you a better programmer for the rest of your days, even if you never actually use Lisp itself a lot.” . 15 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 15/90.
  16. 16. . 大牛们怎么说 Lisp 的 . Paul Graham . “Within a couple weeks of learning Lisp I found programming in any other language unbearably constraining.” . . Alan Kay . “The greatest single programming language ever designed.” . 16 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 16/90.
  17. 17. . 个人的体验(1) • 曾经对 Lisp 几乎一无所知 ◦ 只听闻它可读性很不好,到处都是括号 • 读《黑客与画家》后,开始有所了解并决定学习 • 以 Common Lisp 开始 Lisp 学习之路 ◦ 《Practical Common Lisp》 ◦ 对 Lisp 独特之处有所体会 ◦ 因为种种原因没能持续 17 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 17/90.
  18. 18. . 个人的体验(2) • 从 Clojure 开始一发不可收拾 ◦ 很短的时间就上手了 • 熟悉 Lisp 就是熟悉了所有的 Lisp 方言 ◦ 更好、更实用的现代 Lisp 方言 ◦ 已经在项目中实际使用 • Emacs Lisp ◦ 因为 Clojure 而开始用 Emacs ◦ 不可避免地要 Hack 一下 Emacs Lisp :-) • SICP 和 Scheme 学习中 ◦ SICP——《计算机程序的解释与构造》 ◦ 学习编程思想,尤其是函数式思维的绝佳教材 ◦ 为什么没能在编程入门阶段阅读到它? 18 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 18/90.
  19. 19. . 括号!括号! • Lisp is … ◦ Lost In Stupid Parentheses ◦ Lots of Irritating Superfluous Parentheses . Lisp Cycles . . 19 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 19/90.
  20. 20. . S-expression • 可以说是括号造就了 Lisp 的强大 • Lisp 代码的这种括号套括号的表达法被称作 S-expression ◦ Lisp 被发明时,还有个 M-expression 的表达方式 ◦ 但 M-expression 不受欢迎,被 S-expression 取代 • S-expression 本质上是任意树形结构的一个表达法 • 让我们先 hold 一会 ◦ 记住 Lisp 代码就是一棵用 S-expression 表达的树 ◦ 看看其他语言是怎么做的 20 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 20/90.
  21. 21. . 其他语言怎么做的 • 具备某种特定语法 • 往往区分 Expression 和 Statement • 词法分析 -> 语法分析 -> AST -> 编译/解释 21 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 21/90.
  22. 22. . Grammer == Restrictions • 必须在语法框架下去写程序 • 无法跳出语法限制下的条条框框 . 在 C 或者 Java 里永远写不出这样的程序 . printf("Result is %s", switch (input) { case 1: "A"; break; case 2: "B"; break; case 3: "C"; break; default: "Unknown"; . }) 22 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 22/90.
  23. 23. . Lisp == No Grammer • Lisp 没有语法 ◦ Lisp 代码只是用 S-expression 表达出来树形结构 ◦ Lisp 求值规则决定了 S-expression 的语义 ◦ 不区分 Expression 和 Statement • 换言之,Lisp 代码其实就相当于其他语言的 AST • Lisp 是真正的“代码即数据” . 没有语法 == 没有限制 . (println "Result is" (case input 1 "A" 2 "B" 3 "C" . "Unknown")) 23 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 23/90.
  24. 24. . 代码即数据 • 应该将 Lisp 代码看作递归的树形结构 • Lisp 语言仅仅定义了如何运行这些数据 24 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 24/90.
  25. 25. . Lisp 求值规则(1) • 先提出几个概念 ◦ atom:原子 • 符号、数字、字符串、关键字等等 ◦ form:可被 Lisp 解释器解释的合法 S-expression • 第一个元素必须是 symbol 25 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 25/90.
  26. 26. . Lisp 求值规则(2) • atom 求值规则 ◦ symbol 求值得到 symbol 上绑定的值 ◦ 其他 atom 求值得到本身 • form 求值规则 ◦ 根据 form 的第一个 symbol 决定语义 26 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 26/90.
  27. 27. . form 的语义由第一个 symbol 决定 • symbol 对应一个函数:函数调用 ◦ 要先将函数所有的参数求值出来才能调用函数 • symbol 对应一个 special form ◦ 根据 special form 来决定语义,且子 form 代表的含义是固定 的 • symbol 对应一个宏 ◦ 调用宏函数来展开宏 ◦ 这一步一般发生在编译阶段,到运行时所有的宏都应该被完 全展开了 27 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 27/90.
  28. 28. . 以上就是 Lisp 的所有求值规则 • 短短几句话就可以总结 Lisp 的运行规则 • Lisp 的核心只有 7、8 个核心原语操作 ◦ 其他的函数、special form 都在此之上实现 ◦ if,while,for,case,defun 等等… • 这些意味着什么? 28 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 28/90.
  29. 29. . Lisp 是最高级的语言 • 用户可以任意定义自己需要的语言功能 ◦ if-not,for-even,defprivate… • 终极 DSL ◦ 直接通过扩展语言来以最自然的方式来表达问题域的概念 • 通过宏来实现 ◦ 和 C/C++ 里的宏完全是两个概念 • C/C++ 里的宏是简单的字符串替换 • Lisp 的宏是以 Lisp 代码为输入/输出的函数 • Lisp 语言在任何阶段都是可用的 ◦ Read -> Compile -> Eval ◦ Reader Macro ◦ Macro 29 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 29/90.
  30. 30. . 你可以任意发明自己的语言构造 • 不如发明个 while-not 玩玩? (defmacro while-not [test & body] `(while (not ~test) ~@body)) (let [a (atom 10)] (while-not (< @a 0) (println @a) (swap! a - 2))) ; ==> 打印 10 8 6 4 2 0 30 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 30/90.
  31. 31. . 示例——clojure-control (defcluster :mycluster :clients [{:host "a.domain.com" :user "alogin"} {:host "b.domain.com" :user "blogin"}]) (deftask :deploy "scp files to remote machines" [file1 file2] (scp [file1 file2] "/home/alogin/") (ssh (str "tar zxvf " file1)) (ssh (str "tar zxvf " file2))) ; control run CLUSTER TASK <args> 31 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 31/90.
  32. 32. . 示例——clojure.java.jdbc (def mysql-db {:subprotocol "mysql" :subname "//127.0.0.1:3306/clojure_test" :user "clojure_test" :password "clojure_test"}) (sql/with-connection mysql-db (sql/insert-records :fruit {:name "Apple" :appearance "rosy" :cost 24} {:name "Orange" :appearance "round" :cost 49})) 32 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 32/90.
  33. 33. . 示例——clojure web 开发 (defpage [:post "/"] {:keys [url]} (layout [:h3 "Here is your shortened URL"] [:h3 (short-url-link (shorten-url! url))] [:p (link-to "/" "Back")])) 33 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 33/90.
  34. 34. . 个人实践——clj.tr069 (deftr069type Inform (device-id :child :DeviceId) (events :child-array :Event :EventStruct) (parameter-list :child-array :ParameterList :ParameterValueStruct) (retry-count :int :RetryCount) (current-time :dateTime :CurrentTime) (max-envelopes :int :MaxEnvelopes)) 34 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 34/90.
  35. 35. . Topic 今天的主题 Clojure 简介 揭开 Lisp 的神秘面纱 Clojure 语言特性一览 函数式编程 并发支持 35 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 35/90.
  36. 36. . 基本数据类型 variable1 ;Symbols(可理解成别的语言的变量) "string" ;String :keyword ;Keyword i ;Character true false ;Booleans nil ; 等同 Java 的 null 1234 ;long 12.4 ;double 42N ;BitInt 0.01M ;BigDecimal 22/7 ;clojure.lang.Ratio (分数) #"(d+)-(d+)" ;Regex 36 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 36/90.
  37. 37. . 复合数据类型 (1 2 3 4 5) ;List [1 2 3 4 5] ;Vector {:name "John" :age 22} ;Map #{"a" "b" "c"} ;Set • Clojure 直接支持 Map,Vector 和 Set 的字面量 ◦ 其他 Lisp 方言可能要借助函数来创建 ◦ 对开发者更友好 37 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 37/90.
  38. 38. . 命名空间 • 类似 Java 的 package,但 Java 的 package 中不允许有数据 ◦ 更像 Python、Ruby 中的 Module • 把代码和数据组织到不同的命名空间下 • 可以互相引用 • 默认命名空间是 user • 通过 ns 宏来声明一个模块所处的命名空间 38 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 38/90.
  39. 39. . 定义全局变量 (def my-name "Foobar") my-name ; ==> "Foobar" user/my-name ; ==> "Foobar" • 将指定的值绑定到指定的 Symbol 上 • 通常用作全局数据,初始化后不再变动 39 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 39/90.
  40. 40. . 定义函数 (def hello-func (fn [name] (println "Hello, " name))) ; 把函数理解成数据 (hello-func "World") (defn hello-func [name] (println "Hello, " name)) ; 和上面的定义方式等价 • Lisp 语言核心很小,很多在别的语言里是语言级别功能的, 在 Lisp 里都是通过更基础的操作组合的 ◦ defn 其实就是 def 后面跟着一个 fn 作为值 40 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 40/90.
  41. 41. . 函数的返回值 • 没有显式的 return ◦ Ruby 等语言也学到了这一思想 • 逻辑上最后一条表达式的结果即函数的返回值 ◦ 不区分语句和表达式,一切皆有值 • 任何 Lisp form 求值后一定会得到一个值 ◦ 比如 if ,满足条件返回 then 求值的结果,否则是 else 求值 的结果 ◦ 比如 do ,返回最后一个表达式的值 ◦ 递归 • 有一些操作返回的是 nil ,这类 form 主要是为了产生副 作用而不是值 ◦ 比如在 doseq 里写文件、打印日志等 41 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 41/90.
  42. 42. . 匿名函数 • fn 创建的函数可以是匿名的 ◦ 如果用 def 赋值给一个 symbol,相当于为其命名 • 可以在需要函数的地方直接用 fn 创建匿名函数 ◦ 如果需要递归,可以在 fn 后面给它一个名字,但这个名字只 在函数体中有效 42 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 42/90.
  43. 43. . 用 #(...) 来创建匿名函数 • 可以用 #(...) 来创建更简洁的匿名函数 ◦ 可以用函数体只有一个 form 的地方 ◦ 使用 % 以及 %n 来代表参数 (fn [x] (.toUpperCase x)) #(.toUpperCase %) (fn [a b] (* (+ a b) (- a b)) #(* (+ %1 %2) (- %1 %2)) 43 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 43/90.
  44. 44. . 局部变量的使用——let (let [a 3 b 4 c (* a b)] ; 引用已定义的 symbol a 和 b (println a b c)) ; Body 中可以引用 let 中绑定的所有 symbol • 定义局部变量 • 将值绑定到指定的 symbol 上,在 let 的 body 部分可以引 用这些 symbol ◦ 可一次绑定多个 symbol ◦ 后面的 binding form 中可以引用前面已经定义过的 symbol • 绑定是只读的,一旦绑定无法变更 ◦ Clojure 强调“不可变性”的体现 44 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 44/90.
  45. 45. . let 可以任意嵌套 (let [a 3 b 4] (let [a 5 b (+ b 1) c (* a b)] (println a b c))) • Clojure 采用的是词法作用域,symbol 引用的是最近的 scope 里的值 • let 的 [..] 部分被成为 binding form ◦ 其他的 binding form 还有函数参数 ◦ binding 语句 • 嵌套的 binding form 都采用上述规则 45 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 45/90.
  46. 46. . Destructuring 介绍 • Destructuring 是 Lisp 语言的一个十分强大的功能 • 用来将“解构”复合数据结构并绑定到 symbol 上 • 可以用在任意 binding form 上 ◦ fn、defn、let、binding 等等 • 支持所有的数据结构 ◦ list、vector、map、set • Destructuring 还支持嵌套 46 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 46/90.
  47. 47. . Destructuring 示例 (let [data [:a :b :c] a (first data) (let [data [:a :b :c] b (second data) V.S [a b c] data] c (last data)] (println a b c)) (println a b c)) 47 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 47/90.
  48. 48. . Map 的 Destructuring (defn print-info [{:keys [name gender age]}] (println name gender age)) (print-info {:name "John" :gender "Male" :age 32}) ; ==> John Male 32 • Destructuring 让 Map 十分适合作为数据的承载对象 48 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 48/90.
  49. 49. . 条件操作符 if (defn test-value [x] (if (> x 0) (println "Yeah") (println "No"))) (test-value 1) (test-value 0) 49 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 49/90.
  50. 50. . then/else 支持多个表达式 (defn test-value2 [x] (if (> x 0) (do (println "Yeah") (println "Right")) (println "No"))) (test-value2 1) (test-value2 0) • 如果 then/else 部分有多个表达式,则需要用 do 包装一下 • do 就类似 C/Java 中的大括号 50 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 50/90.
  51. 51. . 条件操作符 when (defn test-value3 [x] (when (> x 0) (println "Yeah") (println "Right"))) (test-value3 1) • 相当于没有 else 的 if • 可以直接写多条表达式 51 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 51/90.
  52. 52. . 循环——loop/recur (defn print-1-to-10 [] (loop [x 1] (when (<= x 10) (println x) (recur (+ x 1))))) (print-1-to-10) ; ==> 打印 1 到 10 • loop 定义递归点以及 symbol 初始值 • recur 跳转到 loop 处,并以参数值来更新 loop 处的 symbol 绑定 • recur 必须是逻辑上的最后一句语句,后面没有任何别的 表达式 52 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 52/90.
  53. 53. . 在函数中使用 recur (defn print-x-to-10 [x] (when (<= x 10) (println x) (recur (+ x 1)))) (print-x-to-10 5) • 函数入口也可以作为一个递归点(相当于有个隐藏的 loop ) 53 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 53/90.
  54. 54. . loop/recur 可读性问题 • loop/recur 的可读性不好 • 是为了解决 JVM 不支持 TCO(尾递归优化 Tail Call Optimization)的问题 • 实践中往往有更高层次的选择,doseq , for, map 等 • 很少有直接使用 loop/recur 的需要 (defn print-y-to-10 [y] (doseq [x (range y 11)] (println x))) (print-y-to-10 5) 54 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 54/90.
  55. 55. . 和 Java 互操作 • java.lang 包以外的类需要 import 才能用 • 通过 new 操作符来创建 Java 对象 • 也可以使用 ClassName. 宏,它往往更简洁 • 使用 .methodName 来调用实例方法,对象作为第一个参数 • 使用 Class/methodName 来调用静态方法 • 特殊的内部类——无法像在 Java 中那样直接使用 ◦ 需要使用 ParentClass$ChildClass 的形式使用 ◦ 事实上这才是内部类的真实面目 55 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 55/90.
  56. 56. . 和 Java 互操作——示例 (import java.util.Calendar java.util.Date java.text.SimpleDateFormat) (let [date (new Date) cal (Calendar/getInstance) fmt (SimpleDateFormat. "yyyy-MM-dd HH:mm:ss")] (println (.toString date)) (doto cal (.set Calendar/YEAR 2011) (.clear Calendar/SECOND)) (println (.format fmt (.getTime cal)))) 56 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 56/90.
  57. 57. . Topic 今天的主题 Clojure 简介 揭开 Lisp 的神秘面纱 Clojure 语言特性一览 函数式编程 并发支持 57 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 57/90.
  58. 58. . 高阶函数 • 能使用高阶函数的条件是语言支 . 持 First-class Function 什么是高阶函数? ◦ 函数也是“一类对象” . ◦ 可以作为变量的值 参数或者返回值是函数 ◦ 可以作为参数传递 的函数就是高阶函数 ◦ 可以作为返回值 (Higher-Order • 支持 First-class Function 的语言: Function)。map , filter ◦ 所有 Lisp 方言 , reduce 都是典型的高 ◦ JavaScript 阶函数。 . ◦ Groovy,Scala • 高阶函数是函数式编程的基石 58 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 58/90.
  59. 59. . map (map count ["Foo" "Hello" "World"]) ; ==> (3 5 5) (map #(.toUpperCase %) ["a" "B" "c" "d"]) ; ==> ("A" "B" "C" "D") . map 函数的语义 . • map 对输入的集合作变换操作 ◦ 对输入集合的每个元素应用给定的函数 ◦ 得到的集合包含变换后的元素 ◦ 给定函数支持一个参数 . 59 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 59/90.
  60. 60. . filter (filter even? (range 10)) ; ==> (0 2 4 6 8) (filter #(> (count %) 0) ["" "Foobar" "" nil "Hello" "World"]) ; ==> ("Foobar" "Hello" "World") . filter 函数的语义 . • filter 对输入的集合作过滤操作 ◦ 对输入集合的每个元素应用给定函数 ◦ 返回逻辑真的元素会保留在结果集合中 ◦ 给定函数支持一个参数 . 60 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 60/90.
  61. 61. . reduce • 使用给定的函数”折叠“输入集合的所有元素 ◦ 即将多个元素”合并“成一个 ◦ 也常被称作 fold ◦ 可选择给定初值 • reduce 的工作过程 ◦ 对集合的第一、二个元素或者初值、第一个元素应用给定函 数 • 取决于有没有提供初始值 ◦ 不断用得到的中间结果和下一个元素调用给定函数 ◦ 集合遍历结束后返回最后得到的中间结果作为最终结果 • reduce 可以表达很多强大逻辑 61 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 61/90.
  62. 62. . reduce 示例 (reduce + (range 10)) ; ==> 45 (reduce max (map count ["a" "aa" "abc" "ef"])) ; ==> 3 (reduce (fn [m e] (assoc m e (count e))) {} ["america" "china" "japan" "england" "korea"]) ; ==> {korea 5, england 7, japan 5, china 5, america 7} 62 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 62/90.
  63. 63. . 闭包(Closure) (def counter . 什么是闭包? (let [x (atom 0)] . (fn [] 闭包是指绑定了外围局部变量 (swap! x inc)))) 环境的函数,闭包是一种将数 据和行为绑定的方式(对象是 (counter) ; ==> 1 另一种方式) . 。 (counter) ; ==> 2 (counter) ; ==> 3 63 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 63/90.
  64. 64. . 闭包让高阶函数变得实用 • 闭包对函数的使用者是透明的 ◦ 对于调用者来说都是个可调用的“函数” ◦ 例如上面的 counter 调用者对计数器一无所知 • 闭包让函数的组合变得更简单 ◦ 数据和行为的绑定对外界是透明的 ◦ 可以方便地通过部分调用来适配接口 • comp/partial (map (partial * 10) (range 10)) ; ==> (0 10 20 30 40 50 60 70 80 90) (map (comp (partial + 10) (partial * 10)) (range 10)) ; ==> (10 20 30 40 50 60 70 80 90 100) 64 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 64/90.
  65. 65. . 没有闭包的语言怎么办? • 没有闭包的函数需要用户自己来考虑数据传递问题 ◦ C 的函数指针一定程度上也类似类似 First-class Function • 对象是穷人的闭包;闭包是穷人的对象。 ◦ Java 只能通过难看的内部类加 final 引用来模拟闭包 65 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 65/90.
  66. 66. . 函数是更高级的抽象 • map/reduce/filter 三个操作即可组合表达各种复杂逻辑 ◦ 函数式是比面向对象更高级的抽象 • 函数式编程风格不应该看到太多 for 循环之类的构造 ◦ 事实上,Clojure 中的 for 和 Java/C/C++ 等语言的 for 是 完全两码事 • 函数式风格以数据为中心 ◦ 函数无副作用,以数据为输入、以数据为输出 ◦ 通过对数据进行变换、过滤和折叠来表达复杂逻辑 ◦ map/reduce/filter 只是最常用的三个数据操作函数,是最 “通用”的,但实际上有很多更加具体的实用函数 • 排序,分组,多个数据结构的组合 • 查看源码就能发现,它们大多也是用 map/reduce/filter 实现 的 66 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 66/90.
  67. 67. . 数据驱动编程 • 示例:查找给定目录中尺寸最大的文件 (defn find-largest-file [dir] (reduce (fn [f1 f2] (if (> (second f1) (second f2)) f1 f2)) (map (fn [f] [(.getName f) (.length f)]) (filter #(.isFile %) (.listFiles (io/file dir)))))) 67 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 67/90.
  68. 68. . -> 和 ->> 宏 • 使用了 Threading 宏(-> 和 ->> )后,可读性提升很多 • 很好反映出数据在计算步骤之间的流动 (defn find-largest-file-ex [dir] (->> (io/file dir) .listFiles (filter #(.isFile %)) (map (fn [f] [(.getName f) (.length f)])) (reduce (fn [f1 f2] (if (> (second f1) (second f2)) f1 f2))))) 68 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 68/90.
  69. 69. . 继续增强——寻找 Top-N 个最大的文件 (defn find-top-n-files [dir n] (->> (io/file dir) .listFiles (filter #(.isFile %)) (map (fn [f] [(.getName f) (.length f)])) (sort-by (comp - second)) (take n) (map (fn [[name size]] [name (cond (> size 1048576) (str (quot size 1048576) "MB") (> size 1024) (str (quot size 1024) "KB") true (str size "B"))])))) 69 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 69/90.
  70. 70. . 自定义高阶函数 • map/filter/reduce 已经能解决很多问题 • 但当发现代码中有一些可复用的操作时 ◦ 进一步抽象 ◦ 定义自己的高阶函数 70 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 70/90.
  71. 71. . 继续增强——抽取 take-top 函数 (defn take-top [keyfn n coll] (take n (sort-by keyfn coll))) (defn find-top-n-files-ex [dir n] (->> (io/file dir) .listFiles (filter #(.isFile %)) (map (fn [f] [(.getName f) (.length f)])) (take-top (comp - second) n) (map (fn [[name size]] [name (cond (> size 1048576) (str (quot size 1048576) "MB") (> size 1024) (str (quot size 1024) "KB") true (str size "B"))])))) 71 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 71/90.
  72. 72. . Sequence 抽象 • 类似 Java/Python 里的迭代器概念 ◦ map, vector, list, set 等都可以被当作 seq 使用 ◦ 应该理解为对顺序产生数据的抽象 ◦ 不一定 和某个数据结构关联 • sequence 是 Clojure 的核心之一 ◦ 设计优雅 ◦ 功能强大 ◦ 想玩好 Clojure,必须深入理解 sequence • lazy sequence ◦ 让函数式编程在表达力强大的同时性能也很好 ◦ 针对数据的操作如 map, filter 等返回的都是 lazy seq 72 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 72/90.
  73. 73. . Laziness • 通过 lazy-seq 函数可以构造 Lazy Sequence • 只有在需要 sequence 的值的时候才会运算 • Clojure 中针对 sequence 的大部分操作都是 lazy 的 • 通过 Lazy 运算可以支持无限长度的 sequence ◦ range/repeat/iterate 等函数返回的都可能是无限 sequence ◦ 在代码中通过 take 等函数来获取其中的一部分 73 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 73/90.
  74. 74. . 例子:解析 nginx 日志获取访问排名数据 • 截取字段 (ns nginx (:require [clojure.java.io :as io])) (def ^:private nginx-log-regex #"^([0-9.]+) - - [(.*)] "([A-Z]+) (.*) HTTP/(1.[01 (defn extract-log-fields "Extract fields from a line of nginx log" [line] (vec (->> line (re-seq nginx-log-regex) first rest))) 74 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 74/90.
  75. 75. . 解析 nginx 日志获取访问排名数据 • 核心逻辑 (defn parse-ip-freq [file black-list n] (with-open [rdr (io/reader file)] (let [black-list (set black-list)] (->> (line-seq rdr) (map (comp first extract-log-fields)) (remove #(or (nil? %) (black-list %))) frequencies (take-top (comp - second) n))))) 75 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 75/90.
  76. 76. . 函数式编程——一点总结 • 代码逻辑体现为数据在计算步骤之间的流动 • 每个计算步骤都是一个函数调用 ◦ 这些函数是没有副作用的 ◦ 可以组合、变换这些函数以获得需要的功能 • 数据以 sequence 为载体 ◦ 不一定是内存中具体数据结构 ◦ 计算/IO 过程的抽象 ◦ 甚至是将更复杂的结构表达为序列 • 树的遍历 • 计算是 Lazy 的 ◦ 只有当数据被消费的时候才会触发计算步骤的执行 ◦ 没有额外的内存/GC 开销 76 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 76/90.
  77. 77. . Topic 今天的主题 Clojure 简介 揭开 Lisp 的神秘面纱 Clojure 语言特性一览 函数式编程 并发支持 77 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 77/90.
  78. 78. . 简单的异步支持——future • 将 body 部分的代码放到异步线程池里执行,并返回一个对 应的 Future 对象 ◦ 通过 deref 来引用 future 的返回值 ◦ 方便的 reader macro @ 和 deref 等价 (defn async-hello [] (future (Thread/sleep 5000) (println "Hello") (* 10 10))) (def a (async-hello)) (deref a) @a 78 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 78/90.
  79. 79. . 简单利用多核来做并行计算——pmap • 和 map 语义是一致的,但在异步线程池中半 lazy 地执行 • 数据在线程之间的交换是由 Clojure 来解决的,无需用户干 预 ◦ 线程安全无需自己考虑 • 在多核机器上能获得很大性能提升 ◦ 一般需要将输入划分成合适的 chunk 来并行执行 ◦ chunk size 的调整也很重要 79 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 79/90.
  80. 80. . 访问排名示例的 pmap 强化版 (defn parse-ip-freq-pmap [file black-list n] (with-open [rdr (io/reader file)] (let [lines (line-seq rdr) chunks (partition-all 5000 lines) black-list (set black-list)] (->> chunks (pmap (fn [chunk] (->> chunk (map (comp first extract-log-fields)) (remove #(or (nil? %) (black-list %))) frequencies))) (reduce (partial merge-with +)) (take-top (comp - second) n))))) 80 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 80/90.
  81. 81. . 原子类型——atoms • 类似 Java 中的 AtomicReference • 可以引用任意数据类型 • 通过 swap!/compare-and-set!/reset! 来操作 (def counter (let [x (atom 0)] (fn [] (swap! x inc)))) 81 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 81/90.
  82. 82. . STM 支持——refs • 软件事务内存 • 通过类似数据库事务的机制来操作内存数据 • 数据事务可以保证 ACID,软件事务内存可以保证 ACI,D 无法保证 • ref 代表一个需要通过事务访问的引用 • 所有对 ref 的访问都需要包装在事务中 ◦ 通过 dosync 包装事务操作 • 事务提交时发现冲突会导致事务被重试 ◦ 因此 dosync 中的代码可能被执行多次 ◦ 必须是无副作用的,否则会有逻辑问题 82 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 82/90.
  83. 83. . 示例:银行转帐 (defn transfer-money [from to amount] (dosync (alter from - amount) (alter to + amount))) (def my-account (ref 1000)) (def your-account (ref 1000)) (transfer-money my-account your-account 400) @my-account ;==> 600 @your-account ;==> 1400 83 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 83/90.
  84. 84. . 异步支持——agents • 通过异步操作的原子引用 • 使用 send 和 send-off 来发起异步的 agent action • agent action 会排队在 agent thread pool 里执行 • agent action 执行的结果会被原子地更新到 agent 里 • 支持 STM,事务中执行的 send 和 send-off 会保证不会 重复提交 agent action 84 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 84/90.
  85. 85. . Clojure 并发编程——一点总结 • 这里只是大概介绍一些最基本的内容 ◦ 最常用的还是 atom ◦ 需要用到 STM 的场景其实不多(我没遇到过) ◦ 坚持用无副作用的函数式风格本身对并发就有很大帮助,不 一定非要引入这些机制 • 想用好需要花功夫深入探索 ◦ Concurrency is hard! ◦ STM 也有其自身的问题,不是万能的 85 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 85/90.
  86. 86. . 今天没有谈到的话题 • 数据结构的更多操作 ◦ 重在介绍思想而不是具体的 API ◦ 可以查文档来进一步学习 ◦ 一定要查文档,标准库替你做的比你想象的多很多 • 多态机制 ◦ defprotocol 和 defrecord ,接口和自定义数据类型 ◦ Multi-method,更加灵活的动态派发机制,完爆 OO 几条街 • 宏 ◦ 上面提到的各种 deftask/defcluster/defpage/deftr069type 等等都需要通 过宏来实现 ◦ 不需要宏也能走很远 ◦ 用在需要 DSL 的场合 86 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 86/90.
  87. 87. . 今天没有谈到的话题 • 工具、环境 ◦ 构建工具:leiningen ◦ Eclipse IDE:Counterclockwise ◦ Emacs clojure-mode • 开始探索的时候自己学习就好 ◦ 网络上遍布各种教程 87 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 87/90.
  88. 88. . Clojure 书籍 • 基础阶段 ◦ Programming Clojure 2nd Edition ◦ Clojure Programming • 进阶阶段 ◦ The Joy of Clojure • 都有人在翻译,其中前两本应该快要出版了 88 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 88/90.
  89. 89. . Clojure 网络资源 • 官网:http://clojure.org • CN-Clojure:https://groups.google.com/group/cn-clojure/ ◦ 国内的 Clojure 牛人都活跃在此 • Clojure 中文维基:http://wiki.clojure.cn/ • ClojureDocs:http://clojuredocs.org/ • CDS Project:http://clojure-doc.org/ ◦ 各种教程 • Clojure Handbook: http://qiujj.com/static/clojure-handbook.html ◦ 少有的完整中文手册,强烈推荐 89 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 89/90.
  90. 90. . Happy Hacking Lisp 90 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 90/90.

×