SlideShare une entreprise Scribd logo
1  sur  90
Télécharger pour lire hors ligne
.




                 JVM 上的实用 Lisp 方言:Clojure

                          Jerry Peng


                          2012-12-18




                                                     1/
    Jerry Peng            JVM 上的实用 Lisp 方言:Clojure      90
                                                     1/90
.
.




    Outline

        今天的主题

        Clojure 简介

        揭开 Lisp 的神秘面纱

        Clojure 语言特性一览

        函数式编程

        并发支持


                                                    2/
    Jerry Peng           JVM 上的实用 Lisp 方言:Clojure      90
                                                    2/90
.
.




    Topic

        今天的主题

        Clojure 简介

        揭开 Lisp 的神秘面纱

        Clojure 语言特性一览

        函数式编程

        并发支持


                                                    3/
    Jerry Peng           JVM 上的实用 Lisp 方言:Clojure      90
                                                    3/90
.
.




    思维方式的引导



         • Clojure 语言的简要介绍
           ◦ Lisp 的特别之处
         • 一窥函数式编程的奇妙
         • 重在打开一扇思维方式的门
           ◦ 私下的学习和实践最重要




                                                    4/
    Jerry Peng           JVM 上的实用 Lisp 方言:Clojure      90
                                                    4/90
.
.




    Topic

        今天的主题

        Clojure 简介

        揭开 Lisp 的神秘面纱

        Clojure 语言特性一览

        函数式编程

        并发支持


                                                    5/
    Jerry Peng           JVM 上的实用 Lisp 方言:Clojure      90
                                                    5/90
.
.




    About Clojure



                    • Clojure 发音和 Closure 一样
                    • 作者是 Rich Hickey
                    • 最新稳定版本是 1.4
                      ◦ 1.5RC1 也已经 release 了




                                                   6/
    Jerry Peng          JVM 上的实用 Lisp 方言:Clojure      90
                                                   6/90
.
.




    About Rich Hickey




                   • 十分有想法的一个人
                   • 强烈推荐看看他关于 Time、Identity 和
                     Value 的那场演讲
                   • E2E 上和 Brian Beckman 的讨论




                                                   7/
    Jerry Peng          JVM 上的实用 Lisp 方言:Clojure      90
                                                   7/90
.
.




    Hello World



        (defn hello [name]
          (println "Hello," name))

        (hello "John")
        ; ==> Hello, John




                                                        8/
    Jerry Peng               JVM 上的实用 Lisp 方言:Clojure      90
                                                        8/90
.
.




    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
.
.




    Clojure 语言简介



         • Clojure 是一种 Lisp 方言
         • Clojure 是函数式编程语言
         • Clojure 支持强大的并发机制
         • 强大的 REPL 支持(Read-Evaluation-Print-Loop)
           ◦ 极其高效、舒适的开发体验




                                                       10 /
    Jerry Peng              JVM 上的实用 Lisp 方言:Clojure       90
                                                       10/90
.
.




    Clojure 是一种 Lisp 方言

         • 100% 的 Lisp
           ◦ REPL 支持
           ◦ 强大的宏
         • 更现代
           ◦ 支持 vector 和 map 的字面量
           ◦ 括号尽可能少
         • 平台更强大——JVM/CLR
           ◦ 性能强大、工业标准
           ◦ 高质量的库支持
           ◦ 无缝的 Java 互操作
           ◦ ClojureScript,Clojure-Py



                                                             11 /
    Jerry Peng                    JVM 上的实用 Lisp 方言:Clojure       90
                                                             11/90
.
.




    Clojure 是函数式编程语言


         • 强调不可变性(Immutability)
         • 性能优秀的 Persistent Data Structure
           ◦ 做到 immutable 的同时兼顾性能
         • 强大的 sequence 支持
           ◦ 类似 Python 的 iterator,但更强大
         • Laziness
           ◦ Lazy sequences
           ◦ 大量的基本操作都是 lazy 的
           ◦ 自由使用函数式编程风格又无需担心性能




                                                         12 /
    Jerry Peng                JVM 上的实用 Lisp 方言:Clojure       90
                                                         12/90
.
.




    Clojure 对并发有良好的支持



         • Immutability 本身就对并发友好
            ◦ 可变状态是并发的天敌
         • 简单的原子操作支持(atoms)
         • 软件事务内存(STM,refs)
         • 异步编程(agents)




                                                     13 /
    Jerry Peng            JVM 上的实用 Lisp 方言:Clojure       90
                                                     13/90
.
.




    Topic

        今天的主题

        Clojure 简介

        揭开 Lisp 的神秘面纱

        Clojure 语言特性一览

        函数式编程

        并发支持


                                                    14 /
    Jerry Peng           JVM 上的实用 Lisp 方言:Clojure       90
                                                    14/90
.
.




    大牛们怎么说 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
.
.




    大牛们怎么说 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
.
.




    个人的体验(1)


         • 曾经对 Lisp 几乎一无所知
           ◦ 只听闻它可读性很不好,到处都是括号
         • 读《黑客与画家》后,开始有所了解并决定学习
         • 以 Common Lisp 开始 Lisp 学习之路
           ◦ 《Practical Common Lisp》
           ◦ 对 Lisp 独特之处有所体会
           ◦ 因为种种原因没能持续




                                                 17 /
    Jerry Peng        JVM 上的实用 Lisp 方言:Clojure       90
                                                 17/90
.
.




    个人的体验(2)

         • 从 Clojure 开始一发不可收拾
           ◦ 很短的时间就上手了
                 •   熟悉 Lisp 就是熟悉了所有的 Lisp 方言
             ◦ 更好、更实用的现代 Lisp 方言
             ◦ 已经在项目中实际使用
         • Emacs Lisp
           ◦ 因为 Clojure 而开始用 Emacs
           ◦ 不可避免地要 Hack 一下 Emacs Lisp :-)
         • SICP 和 Scheme 学习中
           ◦ SICP——《计算机程序的解释与构造》
           ◦ 学习编程思想,尤其是函数式思维的绝佳教材
           ◦ 为什么没能在编程入门阶段阅读到它?


                                                             18 /
    Jerry Peng                    JVM 上的实用 Lisp 方言:Clojure       90
                                                             18/90
.
.




    括号!括号!

         • Lisp is …
           ◦ Lost In Stupid Parentheses
           ◦ Lots of Irritating Superfluous Parentheses
        .
        Lisp Cycles
        .




        .

                                                               19 /
    Jerry Peng                      JVM 上的实用 Lisp 方言:Clojure       90
                                                               19/90
.
.




    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
.
.




    其他语言怎么做的




         • 具备某种特定语法
         • 往往区分 Expression 和 Statement
         • 词法分析 -> 语法分析 -> AST -> 编译/解释




                                                        21 /
    Jerry Peng               JVM 上的实用 Lisp 方言:Clojure       90
                                                        21/90
.
.




    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
.
.




    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
.
.




    代码即数据


         • 应该将 Lisp 代码看作递归的树形结构
         • Lisp 语言仅仅定义了如何运行这些数据




                                                 24 /
    Jerry Peng        JVM 上的实用 Lisp 方言:Clojure       90
                                                 24/90
.
.




    Lisp 求值规则(1)



         • 先提出几个概念
           ◦ atom:原子
                 •   符号、数字、字符串、关键字等等
             ◦ form:可被 Lisp 解释器解释的合法 S-expression
                 •   第一个元素必须是 symbol




                                                            25 /
    Jerry Peng                   JVM 上的实用 Lisp 方言:Clojure       90
                                                            25/90
.
.




    Lisp 求值规则(2)



         • atom 求值规则
           ◦ symbol 求值得到 symbol 上绑定的值
           ◦ 其他 atom 求值得到本身
         • form 求值规则
           ◦ 根据 form 的第一个 symbol 决定语义




                                                      26 /
    Jerry Peng             JVM 上的实用 Lisp 方言:Clojure       90
                                                      26/90
.
.




    form 的语义由第一个 symbol 决定


         • symbol 对应一个函数:函数调用
           ◦ 要先将函数所有的参数求值出来才能调用函数
         • symbol 对应一个 special form
           ◦ 根据 special form 来决定语义,且子 form 代表的含义是固定
             的
         • symbol 对应一个宏
           ◦ 调用宏函数来展开宏
           ◦ 这一步一般发生在编译阶段,到运行时所有的宏都应该被完
             全展开了




                                                      27 /
    Jerry Peng            JVM 上的实用 Lisp 方言:Clojure        90
                                                      27/90
.
.




    以上就是 Lisp 的所有求值规则



         • 短短几句话就可以总结 Lisp 的运行规则
         • Lisp 的核心只有 7、8 个核心原语操作
           ◦ 其他的函数、special form 都在此之上实现
           ◦ if,while,for,case,defun 等等…
         • 这些意味着什么?




                                                     28 /
    Jerry Peng            JVM 上的实用 Lisp 方言:Clojure       90
                                                     28/90
.
.




    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
.
.




    你可以任意发明自己的语言构造

         • 不如发明个 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
.
.




    示例——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
.
.




    示例——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
.
.




    示例——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
.
.




    个人实践——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
.
.




    Topic

        今天的主题

        Clojure 简介

        揭开 Lisp 的神秘面纱

        Clojure 语言特性一览

        函数式编程

        并发支持


                                                    35 /
    Jerry Peng           JVM 上的实用 Lisp 方言:Clojure       90
                                                    35/90
.
.




    基本数据类型

        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
.
.




    复合数据类型


        '(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
.
.




    命名空间



         • 类似 Java 的 package,但 Java 的 package 中不允许有数据
           ◦ 更像 Python、Ruby 中的 Module
         • 把代码和数据组织到不同的命名空间下
         • 可以互相引用
         • 默认命名空间是 user
         • 通过 ns 宏来声明一个模块所处的命名空间




                                                        38 /
    Jerry Peng             JVM 上的实用 Lisp 方言:Clojure         90
                                                        38/90
.
.




    定义全局变量


        (def my-name "Foobar")
        my-name
        ; ==> "Foobar"
        user/my-name
        ; ==> "Foobar"

         • 将指定的值绑定到指定的 Symbol 上
         • 通常用作全局数据,初始化后不再变动




                                                        39 /
    Jerry Peng               JVM 上的实用 Lisp 方言:Clojure       90
                                                        39/90
.
.




    定义函数


        (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
.
.




    函数的返回值

         • 没有显式的 return
           ◦ Ruby 等语言也学到了这一思想
         • 逻辑上最后一条表达式的结果即函数的返回值
           ◦ 不区分语句和表达式,一切皆有值
         • 任何 Lisp form 求值后一定会得到一个值
           ◦ 比如 if ,满足条件返回 then 求值的结果,否则是 else 求值
             的结果
           ◦ 比如 do ,返回最后一个表达式的值
           ◦ 递归
         • 有一些操作返回的是 nil ,这类 form 主要是为了产生副
             作用而不是值
             ◦ 比如在 doseq 里写文件、打印日志等


                                                     41 /
    Jerry Peng            JVM 上的实用 Lisp 方言:Clojure       90
                                                     41/90
.
.




    匿名函数



         • fn 创建的函数可以是匿名的
           ◦ 如果用 def 赋值给一个 symbol,相当于为其命名
         • 可以在需要函数的地方直接用 fn 创建匿名函数
           ◦ 如果需要递归,可以在 fn 后面给它一个名字,但这个名字只
             在函数体中有效




                                                    42 /
    Jerry Peng           JVM 上的实用 Lisp 方言:Clojure       90
                                                    42/90
.
.




    用 #(...) 来创建匿名函数


         • 可以用 #(...) 来创建更简洁的匿名函数
           ◦ 可以用函数体只有一个 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
.
.




    局部变量的使用——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
.
.




    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
.
.




    Destructuring 介绍


         • Destructuring 是 Lisp 语言的一个十分强大的功能
         • 用来将“解构”复合数据结构并绑定到 symbol 上
         • 可以用在任意 binding form 上
           ◦ fn、defn、let、binding 等等
         • 支持所有的数据结构
           ◦ list、vector、map、set
         • Destructuring 还支持嵌套




                                                          46 /
    Jerry Peng                 JVM 上的实用 Lisp 方言:Clojure       90
                                                          46/90
.
.




    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
.
.




    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
.
.




    条件操作符 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
.
.




    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
.
.




    条件操作符 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
.
.




    循环——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
.
.




    在函数中使用 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
.
.




    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
.
.




    和 Java 互操作


         • java.lang 包以外的类需要 import 才能用
         • 通过 new 操作符来创建 Java 对象
         • 也可以使用 ClassName. 宏,它往往更简洁
         • 使用 .methodName 来调用实例方法,对象作为第一个参数
         • 使用 Class/methodName 来调用静态方法
         • 特殊的内部类——无法像在 Java 中那样直接使用
           ◦ 需要使用 ParentClass$ChildClass 的形式使用
           ◦ 事实上这才是内部类的真实面目




                                                     55 /
    Jerry Peng            JVM 上的实用 Lisp 方言:Clojure       90
                                                     55/90
.
.




    和 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
.
.




    Topic

        今天的主题

        Clojure 简介

        揭开 Lisp 的神秘面纱

        Clojure 语言特性一览

        函数式编程

        并发支持


                                                    57 /
    Jerry Peng           JVM 上的实用 Lisp 方言:Clojure       90
                                                    57/90
.
.




    高阶函数

                              • 能使用高阶函数的条件是语言支
      .                          持 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
.
.




    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
.
.




    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
.
.




    reduce

         • 使用给定的函数”折叠“输入集合的所有元素
           ◦ 即将多个元素”合并“成一个
           ◦ 也常被称作 fold
           ◦ 可选择给定初值
         • reduce 的工作过程
           ◦ 对集合的第一、二个元素或者初值、第一个元素应用给定函
             数
                 •   取决于有没有提供初始值
             ◦ 不断用得到的中间结果和下一个元素调用给定函数
             ◦ 集合遍历结束后返回最后得到的中间结果作为最终结果
         • reduce 可以表达很多强大逻辑



                                                         61 /
    Jerry Peng                JVM 上的实用 Lisp 方言:Clojure       90
                                                         61/90
.
.




    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
.
.




    闭包(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
.
.




    闭包让高阶函数变得实用
         • 闭包对函数的使用者是透明的
           ◦ 对于调用者来说都是个可调用的“函数”
           ◦ 例如上面的 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
.
.




    没有闭包的语言怎么办?



         • 没有闭包的函数需要用户自己来考虑数据传递问题
           ◦ C 的函数指针一定程度上也类似类似 First-class Function
         • 对象是穷人的闭包;闭包是穷人的对象。
           ◦ Java 只能通过难看的内部类加 final 引用来模拟闭包




                                                      65 /
    Jerry Peng             JVM 上的实用 Lisp 方言:Clojure       90
                                                      65/90
.
.




    函数是更高级的抽象
         • 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
.
.




    数据驱动编程

         • 示例:查找给定目录中尺寸最大的文件

        (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
.
.




    -> 和 ->> 宏

         • 使用了 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
.
.




    继续增强——寻找 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
.
.




    自定义高阶函数




         • map/filter/reduce 已经能解决很多问题
         • 但当发现代码中有一些可复用的操作时
           ◦ 进一步抽象
           ◦ 定义自己的高阶函数




                                                    70 /
    Jerry Peng           JVM 上的实用 Lisp 方言:Clojure       90
                                                    70/90
.
.




    继续增强——抽取 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
.
.




    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
.
.




    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
.
.




    例子:解析 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
.
.




    解析 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
.
.




    函数式编程——一点总结

         • 代码逻辑体现为数据在计算步骤之间的流动
         • 每个计算步骤都是一个函数调用
           ◦ 这些函数是没有副作用的
           ◦ 可以组合、变换这些函数以获得需要的功能
         • 数据以 sequence 为载体
           ◦ 不一定是内存中具体数据结构
           ◦ 计算/IO 过程的抽象
           ◦ 甚至是将更复杂的结构表达为序列
                 •   树的遍历
         • 计算是 Lazy 的
           ◦ 只有当数据被消费的时候才会触发计算步骤的执行
           ◦ 没有额外的内存/GC 开销


                                                       76 /
    Jerry Peng              JVM 上的实用 Lisp 方言:Clojure       90
                                                       76/90
.
.




    Topic

        今天的主题

        Clojure 简介

        揭开 Lisp 的神秘面纱

        Clojure 语言特性一览

        函数式编程

        并发支持


                                                    77 /
    Jerry Peng           JVM 上的实用 Lisp 方言:Clojure       90
                                                    77/90
.
.




    简单的异步支持——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
.
.




    简单利用多核来做并行计算——pmap


         • 和 map 语义是一致的,但在异步线程池中半 lazy 地执行
         • 数据在线程之间的交换是由 Clojure 来解决的,无需用户干
             预
             ◦ 线程安全无需自己考虑
         • 在多核机器上能获得很大性能提升
           ◦ 一般需要将输入划分成合适的 chunk 来并行执行
           ◦ chunk size 的调整也很重要




                                                   79 /
    Jerry Peng          JVM 上的实用 Lisp 方言:Clojure       90
                                                   79/90
.
.




    访问排名示例的 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
.
.




    原子类型——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
.
.




    STM 支持——refs

         • 软件事务内存
         • 通过类似数据库事务的机制来操作内存数据
         • 数据事务可以保证 ACID,软件事务内存可以保证 ACI,D
           无法保证
         • ref 代表一个需要通过事务访问的引用
         • 所有对 ref 的访问都需要包装在事务中
             ◦ 通过 dosync 包装事务操作
         • 事务提交时发现冲突会导致事务被重试
           ◦ 因此 dosync 中的代码可能被执行多次
           ◦ 必须是无副作用的,否则会有逻辑问题



                                                       82 /
    Jerry Peng              JVM 上的实用 Lisp 方言:Clojure       90
                                                       82/90
.
.




    示例:银行转帐

        (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
.
.




    异步支持——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
.
.




    Clojure 并发编程——一点总结


         • 这里只是大概介绍一些最基本的内容
           ◦ 最常用的还是 atom
           ◦ 需要用到 STM 的场景其实不多(我没遇到过)
           ◦ 坚持用无副作用的函数式风格本身对并发就有很大帮助,不
             一定非要引入这些机制
         • 想用好需要花功夫深入探索
           ◦ Concurrency is hard!
           ◦ STM 也有其自身的问题,不是万能的




                                                   85 /
    Jerry Peng          JVM 上的实用 Lisp 方言:Clojure       90
                                                   85/90
.
.




    今天没有谈到的话题
         • 数据结构的更多操作
           ◦ 重在介绍思想而不是具体的 API
           ◦ 可以查文档来进一步学习
           ◦ 一定要查文档,标准库替你做的比你想象的多很多
         • 多态机制
           ◦ defprotocol 和 defrecord ,接口和自定义数据类型
           ◦ Multi-method,更加灵活的动态派发机制,完爆 OO 几条街
         • 宏
           ◦ 上面提到的各种
             deftask/defcluster/defpage/deftr069type 等等都需要通
             过宏来实现
           ◦ 不需要宏也能走很远
           ◦ 用在需要 DSL 的场合

                                                          86 /
    Jerry Peng               JVM 上的实用 Lisp 方言:Clojure         90
                                                          86/90
.
.




    今天没有谈到的话题



         • 工具、环境
           ◦ 构建工具:leiningen
           ◦ Eclipse IDE:Counterclockwise
           ◦ Emacs clojure-mode
         • 开始探索的时候自己学习就好
           ◦ 网络上遍布各种教程




                                                             87 /
    Jerry Peng                    JVM 上的实用 Lisp 方言:Clojure       90
                                                             87/90
.
.




    Clojure 书籍
         • 基础阶段
           ◦ Programming Clojure 2nd Edition
           ◦ Clojure Programming
         • 进阶阶段
           ◦ The Joy of Clojure
         • 都有人在翻译,其中前两本应该快要出版了




                                                       88 /
    Jerry Peng              JVM 上的实用 Lisp 方言:Clojure       90
                                                       88/90
.
.




    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
.
.




    Happy Hacking Lisp




                                                    90 /
    Jerry Peng           JVM 上的实用 Lisp 方言:Clojure       90
                                                    90/90
.

Contenu connexe

En vedette

Redesigning Common Lisp
Redesigning Common LispRedesigning Common Lisp
Redesigning Common Lispfukamachi
 
Getting started with Clojure
Getting started with ClojureGetting started with Clojure
Getting started with ClojureJohn Stevenson
 
Introduction To Lisp
Introduction To LispIntroduction To Lisp
Introduction To Lispkyleburton
 
Clojure: The Art of Abstraction
Clojure: The Art of AbstractionClojure: The Art of Abstraction
Clojure: The Art of AbstractionAlex Miller
 
Biotechnology (Malay)
Biotechnology (Malay)Biotechnology (Malay)
Biotechnology (Malay)Maliney Pohs
 
Patchouli livelihoods info for bop mobiles
Patchouli livelihoods info for bop mobilesPatchouli livelihoods info for bop mobiles
Patchouli livelihoods info for bop mobilesRichard Beresford
 
WebXpress Solutions Profile
WebXpress Solutions ProfileWebXpress Solutions Profile
WebXpress Solutions ProfileWebXpress
 
Home away from home sjc-shopping-observation lab
Home away from home   sjc-shopping-observation labHome away from home   sjc-shopping-observation lab
Home away from home sjc-shopping-observation labViroo Mirji
 
14 Nov 2012 1ABCT Weekly Newsletter
14 Nov  2012 1ABCT Weekly Newsletter14 Nov  2012 1ABCT Weekly Newsletter
14 Nov 2012 1ABCT Weekly NewsletterNoel Waterman
 

En vedette (16)

Redesigning Common Lisp
Redesigning Common LispRedesigning Common Lisp
Redesigning Common Lisp
 
Getting started with Clojure
Getting started with ClojureGetting started with Clojure
Getting started with Clojure
 
DSL in Clojure
DSL in ClojureDSL in Clojure
DSL in Clojure
 
Introduction To Lisp
Introduction To LispIntroduction To Lisp
Introduction To Lisp
 
Clojure: The Art of Abstraction
Clojure: The Art of AbstractionClojure: The Art of Abstraction
Clojure: The Art of Abstraction
 
Prolog & lisp
Prolog & lispProlog & lisp
Prolog & lisp
 
Coearch Survey 2010
Coearch Survey 2010Coearch Survey 2010
Coearch Survey 2010
 
Biotechnology (Malay)
Biotechnology (Malay)Biotechnology (Malay)
Biotechnology (Malay)
 
Covert Audio and the Law
Covert Audio and the LawCovert Audio and the Law
Covert Audio and the Law
 
Patchouli livelihoods info for bop mobiles
Patchouli livelihoods info for bop mobilesPatchouli livelihoods info for bop mobiles
Patchouli livelihoods info for bop mobiles
 
Bbcaudiences
BbcaudiencesBbcaudiences
Bbcaudiences
 
WebXpress Solutions Profile
WebXpress Solutions ProfileWebXpress Solutions Profile
WebXpress Solutions Profile
 
My photo anaylsis
My photo anaylsisMy photo anaylsis
My photo anaylsis
 
Home away from home sjc-shopping-observation lab
Home away from home   sjc-shopping-observation labHome away from home   sjc-shopping-observation lab
Home away from home sjc-shopping-observation lab
 
Manchester
ManchesterManchester
Manchester
 
14 Nov 2012 1ABCT Weekly Newsletter
14 Nov  2012 1ABCT Weekly Newsletter14 Nov  2012 1ABCT Weekly Newsletter
14 Nov 2012 1ABCT Weekly Newsletter
 

Similaire à JVM上的实用Lisp方言:Clojure

Serious clojurescript
Serious clojurescript Serious clojurescript
Serious clojurescript Young Lee
 
Clojure简介与应用
Clojure简介与应用Clojure简介与应用
Clojure简介与应用Robert Hao
 
Java SE 8 技術手冊第 1 章 - Java平台概論
Java SE 8 技術手冊第 1 章 - Java平台概論Java SE 8 技術手冊第 1 章 - Java平台概論
Java SE 8 技術手冊第 1 章 - Java平台概論Justin Lin
 
最新Java技术内存模型
最新Java技术内存模型最新Java技术内存模型
最新Java技术内存模型yiditushe
 
J Ruby和Rails 让Ruby语言融入Java项目
J Ruby和Rails 让Ruby语言融入Java项目J Ruby和Rails 让Ruby语言融入Java项目
J Ruby和Rails 让Ruby语言融入Java项目George Ang
 
Duck Typing and Multiple Inheritance
Duck Typing and Multiple InheritanceDuck Typing and Multiple Inheritance
Duck Typing and Multiple InheritanceSway Wang
 
NSCopying between Objective-C and Swift
NSCopying between Objective-C and SwiftNSCopying between Objective-C and Swift
NSCopying between Objective-C and SwiftMarvin Lin
 
Java SE 7 技術手冊投影片第 01 章 - Java平台概論
Java SE 7 技術手冊投影片第 01 章 - Java平台概論Java SE 7 技術手冊投影片第 01 章 - Java平台概論
Java SE 7 技術手冊投影片第 01 章 - Java平台概論Justin Lin
 
Java Tutorial:Learn Java in 06:00:00
Java Tutorial:Learn Java in 06:00:00Java Tutorial:Learn Java in 06:00:00
Java Tutorial:Learn Java in 06:00:00Justin Lin
 
Nashorn on JDK 8 (ADC2013)
Nashorn on JDK 8 (ADC2013)Nashorn on JDK 8 (ADC2013)
Nashorn on JDK 8 (ADC2013)Kris Mok
 
黑客终极语言——Lisp
黑客终极语言——Lisp黑客终极语言——Lisp
黑客终极语言——LispRui Peng
 
Ruby Rails 老司機帶飛
Ruby Rails 老司機帶飛Ruby Rails 老司機帶飛
Ruby Rails 老司機帶飛Wen-Tien Chang
 

Similaire à JVM上的实用Lisp方言:Clojure (15)

Serious clojurescript
Serious clojurescript Serious clojurescript
Serious clojurescript
 
Clojure简介与应用
Clojure简介与应用Clojure简介与应用
Clojure简介与应用
 
Java SE 8 技術手冊第 1 章 - Java平台概論
Java SE 8 技術手冊第 1 章 - Java平台概論Java SE 8 技術手冊第 1 章 - Java平台概論
Java SE 8 技術手冊第 1 章 - Java平台概論
 
最新Java技术内存模型
最新Java技术内存模型最新Java技术内存模型
最新Java技术内存模型
 
J Ruby和Rails 让Ruby语言融入Java项目
J Ruby和Rails 让Ruby语言融入Java项目J Ruby和Rails 让Ruby语言融入Java项目
J Ruby和Rails 让Ruby语言融入Java项目
 
Duck Typing and Multiple Inheritance
Duck Typing and Multiple InheritanceDuck Typing and Multiple Inheritance
Duck Typing and Multiple Inheritance
 
NSCopying between Objective-C and Swift
NSCopying between Objective-C and SwiftNSCopying between Objective-C and Swift
NSCopying between Objective-C and Swift
 
Java SE 7 技術手冊投影片第 01 章 - Java平台概論
Java SE 7 技術手冊投影片第 01 章 - Java平台概論Java SE 7 技術手冊投影片第 01 章 - Java平台概論
Java SE 7 技術手冊投影片第 01 章 - Java平台概論
 
Java Tutorial:Learn Java in 06:00:00
Java Tutorial:Learn Java in 06:00:00Java Tutorial:Learn Java in 06:00:00
Java Tutorial:Learn Java in 06:00:00
 
RSpec & TDD Tutorial
RSpec & TDD TutorialRSpec & TDD Tutorial
RSpec & TDD Tutorial
 
Nashorn on JDK 8 (ADC2013)
Nashorn on JDK 8 (ADC2013)Nashorn on JDK 8 (ADC2013)
Nashorn on JDK 8 (ADC2013)
 
黑客终极语言——Lisp
黑客终极语言——Lisp黑客终极语言——Lisp
黑客终极语言——Lisp
 
Clojure的魅力
Clojure的魅力Clojure的魅力
Clojure的魅力
 
Ruby Rails 老司機帶飛
Ruby Rails 老司機帶飛Ruby Rails 老司機帶飛
Ruby Rails 老司機帶飛
 
Ruby基础培训
Ruby基础培训Ruby基础培训
Ruby基础培训
 

JVM上的实用Lisp方言:Clojure

  • 1. . JVM 上的实用 Lisp 方言:Clojure Jerry Peng 2012-12-18 1/ Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 1/90 .
  • 2. . Outline 今天的主题 Clojure 简介 揭开 Lisp 的神秘面纱 Clojure 语言特性一览 函数式编程 并发支持 2/ Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 2/90 .
  • 3. . Topic 今天的主题 Clojure 简介 揭开 Lisp 的神秘面纱 Clojure 语言特性一览 函数式编程 并发支持 3/ Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 3/90 .
  • 4. . 思维方式的引导 • Clojure 语言的简要介绍 ◦ Lisp 的特别之处 • 一窥函数式编程的奇妙 • 重在打开一扇思维方式的门 ◦ 私下的学习和实践最重要 4/ Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 4/90 .
  • 5. . Topic 今天的主题 Clojure 简介 揭开 Lisp 的神秘面纱 Clojure 语言特性一览 函数式编程 并发支持 5/ Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 5/90 .
  • 6. . About Clojure • Clojure 发音和 Closure 一样 • 作者是 Rich Hickey • 最新稳定版本是 1.4 ◦ 1.5RC1 也已经 release 了 6/ Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 6/90 .
  • 7. . About Rich Hickey • 十分有想法的一个人 • 强烈推荐看看他关于 Time、Identity 和 Value 的那场演讲 • E2E 上和 Brian Beckman 的讨论 7/ Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 7/90 .
  • 8. . Hello World (defn hello [name] (println "Hello," name)) (hello "John") ; ==> Hello, John 8/ Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 8/90 .
  • 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. . Clojure 语言简介 • Clojure 是一种 Lisp 方言 • Clojure 是函数式编程语言 • Clojure 支持强大的并发机制 • 强大的 REPL 支持(Read-Evaluation-Print-Loop) ◦ 极其高效、舒适的开发体验 10 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 10/90 .
  • 11. . Clojure 是一种 Lisp 方言 • 100% 的 Lisp ◦ REPL 支持 ◦ 强大的宏 • 更现代 ◦ 支持 vector 和 map 的字面量 ◦ 括号尽可能少 • 平台更强大——JVM/CLR ◦ 性能强大、工业标准 ◦ 高质量的库支持 ◦ 无缝的 Java 互操作 ◦ ClojureScript,Clojure-Py 11 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 11/90 .
  • 12. . Clojure 是函数式编程语言 • 强调不可变性(Immutability) • 性能优秀的 Persistent Data Structure ◦ 做到 immutable 的同时兼顾性能 • 强大的 sequence 支持 ◦ 类似 Python 的 iterator,但更强大 • Laziness ◦ Lazy sequences ◦ 大量的基本操作都是 lazy 的 ◦ 自由使用函数式编程风格又无需担心性能 12 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 12/90 .
  • 13. . Clojure 对并发有良好的支持 • Immutability 本身就对并发友好 ◦ 可变状态是并发的天敌 • 简单的原子操作支持(atoms) • 软件事务内存(STM,refs) • 异步编程(agents) 13 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 13/90 .
  • 14. . Topic 今天的主题 Clojure 简介 揭开 Lisp 的神秘面纱 Clojure 语言特性一览 函数式编程 并发支持 14 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 14/90 .
  • 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. . 大牛们怎么说 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. . 个人的体验(1) • 曾经对 Lisp 几乎一无所知 ◦ 只听闻它可读性很不好,到处都是括号 • 读《黑客与画家》后,开始有所了解并决定学习 • 以 Common Lisp 开始 Lisp 学习之路 ◦ 《Practical Common Lisp》 ◦ 对 Lisp 独特之处有所体会 ◦ 因为种种原因没能持续 17 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 17/90 .
  • 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. . 括号!括号! • Lisp is … ◦ Lost In Stupid Parentheses ◦ Lots of Irritating Superfluous Parentheses . Lisp Cycles . . 19 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 19/90 .
  • 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. . 其他语言怎么做的 • 具备某种特定语法 • 往往区分 Expression 和 Statement • 词法分析 -> 语法分析 -> AST -> 编译/解释 21 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 21/90 .
  • 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. . 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. . 代码即数据 • 应该将 Lisp 代码看作递归的树形结构 • Lisp 语言仅仅定义了如何运行这些数据 24 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 24/90 .
  • 25. . Lisp 求值规则(1) • 先提出几个概念 ◦ atom:原子 • 符号、数字、字符串、关键字等等 ◦ form:可被 Lisp 解释器解释的合法 S-expression • 第一个元素必须是 symbol 25 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 25/90 .
  • 26. . Lisp 求值规则(2) • atom 求值规则 ◦ symbol 求值得到 symbol 上绑定的值 ◦ 其他 atom 求值得到本身 • form 求值规则 ◦ 根据 form 的第一个 symbol 决定语义 26 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 26/90 .
  • 27. . form 的语义由第一个 symbol 决定 • symbol 对应一个函数:函数调用 ◦ 要先将函数所有的参数求值出来才能调用函数 • symbol 对应一个 special form ◦ 根据 special form 来决定语义,且子 form 代表的含义是固定 的 • symbol 对应一个宏 ◦ 调用宏函数来展开宏 ◦ 这一步一般发生在编译阶段,到运行时所有的宏都应该被完 全展开了 27 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 27/90 .
  • 28. . 以上就是 Lisp 的所有求值规则 • 短短几句话就可以总结 Lisp 的运行规则 • Lisp 的核心只有 7、8 个核心原语操作 ◦ 其他的函数、special form 都在此之上实现 ◦ if,while,for,case,defun 等等… • 这些意味着什么? 28 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 28/90 .
  • 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. . 你可以任意发明自己的语言构造 • 不如发明个 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. . 示例——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. . 示例——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. . 示例——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. . 个人实践——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. . Topic 今天的主题 Clojure 简介 揭开 Lisp 的神秘面纱 Clojure 语言特性一览 函数式编程 并发支持 35 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 35/90 .
  • 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. . 复合数据类型 '(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. . 命名空间 • 类似 Java 的 package,但 Java 的 package 中不允许有数据 ◦ 更像 Python、Ruby 中的 Module • 把代码和数据组织到不同的命名空间下 • 可以互相引用 • 默认命名空间是 user • 通过 ns 宏来声明一个模块所处的命名空间 38 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 38/90 .
  • 39. . 定义全局变量 (def my-name "Foobar") my-name ; ==> "Foobar" user/my-name ; ==> "Foobar" • 将指定的值绑定到指定的 Symbol 上 • 通常用作全局数据,初始化后不再变动 39 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 39/90 .
  • 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. . 函数的返回值 • 没有显式的 return ◦ Ruby 等语言也学到了这一思想 • 逻辑上最后一条表达式的结果即函数的返回值 ◦ 不区分语句和表达式,一切皆有值 • 任何 Lisp form 求值后一定会得到一个值 ◦ 比如 if ,满足条件返回 then 求值的结果,否则是 else 求值 的结果 ◦ 比如 do ,返回最后一个表达式的值 ◦ 递归 • 有一些操作返回的是 nil ,这类 form 主要是为了产生副 作用而不是值 ◦ 比如在 doseq 里写文件、打印日志等 41 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 41/90 .
  • 42. . 匿名函数 • fn 创建的函数可以是匿名的 ◦ 如果用 def 赋值给一个 symbol,相当于为其命名 • 可以在需要函数的地方直接用 fn 创建匿名函数 ◦ 如果需要递归,可以在 fn 后面给它一个名字,但这个名字只 在函数体中有效 42 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 42/90 .
  • 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. . 局部变量的使用——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. . 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. . 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. . 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. . 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. . 条件操作符 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. . 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. . 条件操作符 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. . 循环——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. . 在函数中使用 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. . 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. . 和 Java 互操作 • java.lang 包以外的类需要 import 才能用 • 通过 new 操作符来创建 Java 对象 • 也可以使用 ClassName. 宏,它往往更简洁 • 使用 .methodName 来调用实例方法,对象作为第一个参数 • 使用 Class/methodName 来调用静态方法 • 特殊的内部类——无法像在 Java 中那样直接使用 ◦ 需要使用 ParentClass$ChildClass 的形式使用 ◦ 事实上这才是内部类的真实面目 55 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 55/90 .
  • 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. . Topic 今天的主题 Clojure 简介 揭开 Lisp 的神秘面纱 Clojure 语言特性一览 函数式编程 并发支持 57 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 57/90 .
  • 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. . 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. . 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. . reduce • 使用给定的函数”折叠“输入集合的所有元素 ◦ 即将多个元素”合并“成一个 ◦ 也常被称作 fold ◦ 可选择给定初值 • reduce 的工作过程 ◦ 对集合的第一、二个元素或者初值、第一个元素应用给定函 数 • 取决于有没有提供初始值 ◦ 不断用得到的中间结果和下一个元素调用给定函数 ◦ 集合遍历结束后返回最后得到的中间结果作为最终结果 • reduce 可以表达很多强大逻辑 61 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 61/90 .
  • 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. . 闭包(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. . 闭包让高阶函数变得实用 • 闭包对函数的使用者是透明的 ◦ 对于调用者来说都是个可调用的“函数” ◦ 例如上面的 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. . 没有闭包的语言怎么办? • 没有闭包的函数需要用户自己来考虑数据传递问题 ◦ C 的函数指针一定程度上也类似类似 First-class Function • 对象是穷人的闭包;闭包是穷人的对象。 ◦ Java 只能通过难看的内部类加 final 引用来模拟闭包 65 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 65/90 .
  • 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. . 数据驱动编程 • 示例:查找给定目录中尺寸最大的文件 (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. . -> 和 ->> 宏 • 使用了 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. . 继续增强——寻找 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. . 自定义高阶函数 • map/filter/reduce 已经能解决很多问题 • 但当发现代码中有一些可复用的操作时 ◦ 进一步抽象 ◦ 定义自己的高阶函数 70 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 70/90 .
  • 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. . 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. . 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. . 例子:解析 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. . 解析 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. . 函数式编程——一点总结 • 代码逻辑体现为数据在计算步骤之间的流动 • 每个计算步骤都是一个函数调用 ◦ 这些函数是没有副作用的 ◦ 可以组合、变换这些函数以获得需要的功能 • 数据以 sequence 为载体 ◦ 不一定是内存中具体数据结构 ◦ 计算/IO 过程的抽象 ◦ 甚至是将更复杂的结构表达为序列 • 树的遍历 • 计算是 Lazy 的 ◦ 只有当数据被消费的时候才会触发计算步骤的执行 ◦ 没有额外的内存/GC 开销 76 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 76/90 .
  • 77. . Topic 今天的主题 Clojure 简介 揭开 Lisp 的神秘面纱 Clojure 语言特性一览 函数式编程 并发支持 77 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 77/90 .
  • 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. . 简单利用多核来做并行计算——pmap • 和 map 语义是一致的,但在异步线程池中半 lazy 地执行 • 数据在线程之间的交换是由 Clojure 来解决的,无需用户干 预 ◦ 线程安全无需自己考虑 • 在多核机器上能获得很大性能提升 ◦ 一般需要将输入划分成合适的 chunk 来并行执行 ◦ chunk size 的调整也很重要 79 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 79/90 .
  • 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. . 原子类型——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. . STM 支持——refs • 软件事务内存 • 通过类似数据库事务的机制来操作内存数据 • 数据事务可以保证 ACID,软件事务内存可以保证 ACI,D 无法保证 • ref 代表一个需要通过事务访问的引用 • 所有对 ref 的访问都需要包装在事务中 ◦ 通过 dosync 包装事务操作 • 事务提交时发现冲突会导致事务被重试 ◦ 因此 dosync 中的代码可能被执行多次 ◦ 必须是无副作用的,否则会有逻辑问题 82 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 82/90 .
  • 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. . 异步支持——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. . Clojure 并发编程——一点总结 • 这里只是大概介绍一些最基本的内容 ◦ 最常用的还是 atom ◦ 需要用到 STM 的场景其实不多(我没遇到过) ◦ 坚持用无副作用的函数式风格本身对并发就有很大帮助,不 一定非要引入这些机制 • 想用好需要花功夫深入探索 ◦ Concurrency is hard! ◦ STM 也有其自身的问题,不是万能的 85 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 85/90 .
  • 86. . 今天没有谈到的话题 • 数据结构的更多操作 ◦ 重在介绍思想而不是具体的 API ◦ 可以查文档来进一步学习 ◦ 一定要查文档,标准库替你做的比你想象的多很多 • 多态机制 ◦ defprotocol 和 defrecord ,接口和自定义数据类型 ◦ Multi-method,更加灵活的动态派发机制,完爆 OO 几条街 • 宏 ◦ 上面提到的各种 deftask/defcluster/defpage/deftr069type 等等都需要通 过宏来实现 ◦ 不需要宏也能走很远 ◦ 用在需要 DSL 的场合 86 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 86/90 .
  • 87. . 今天没有谈到的话题 • 工具、环境 ◦ 构建工具:leiningen ◦ Eclipse IDE:Counterclockwise ◦ Emacs clojure-mode • 开始探索的时候自己学习就好 ◦ 网络上遍布各种教程 87 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 87/90 .
  • 88. . Clojure 书籍 • 基础阶段 ◦ Programming Clojure 2nd Edition ◦ Clojure Programming • 进阶阶段 ◦ The Joy of Clojure • 都有人在翻译,其中前两本应该快要出版了 88 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 88/90 .
  • 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. . Happy Hacking Lisp 90 / Jerry Peng JVM 上的实用 Lisp 方言:Clojure 90 90/90 .