Clojure 快速突击(续)
Clojure 确实要比 Python 语义上庞大, 所以无法尽量在一篇之中收纳进来, 只得另立新篇, 也许还会有第三篇笔记. 现在开始学习函数定义
函数用
和 C 语言一样, 函数必须先定义再使用, 否则要用
提一下 Clojure 的命名规则, 变量和函数名用中划线连接的小写单词.
Clojure 支持可变函数(VarArgs),
函数可包含多个参数及对应的方法体, 以此来实现默认参数或重载
匿名函数已在第一篇中讲过了, 它其实就是一个 Lambda 表达式了, #(...), 参数直接用 %(%1), %2, %3 .... 了. Clojure 的这种方式的匿名函数还是很方便的.
与 Ruby 的约定一样, Clojure 定义谓词型的函数使用问号, 像
学习 Clojure 到现在基本能够体会到它的代码就是数据, 代码中常用的
发现一个有意思的事情, 下面的代码因为输入有误, 结果是 Cloujure 把结果都算出来了, 再告诉我最后多了一个括号, 可见它的执行机制是能匹配到括号就能行, 多了也不是大事. 可以试下
Clojure 使用 Java 的东西可没有 Scala 那么简单, 主要了解几个宏
真正要使用的时候再回过头来看吧, 主要有两点比较新颖: 1)
所有的 Clojure 方法都实现了
循环, 用宏
宏
往下是递归, Clojure 和 Java 一样也是不支持尾递归优化的,
已经不想再往下写了, 先了解这些 Clojure 的基础知识了, 剩下的还有一本 "Clojure IN ACTION" 这本书要细细的品读了.
函数定义
函数用
defn 宏来定义, 函数名与参数列表之间可选的字符串是函数的注释, 相当于 Python 的函数体中第一个字符串, 这个字符串能被 (doc func-name) 列出来, Python 中是用 dir(fun_name 显示函数帮助. 不需要 return 关键字, 和 Groovy/Scala 一样最后一个表达式的值为函数的返回值, 所以函数总是有返回值(或为 nil).和 C 语言一样, 函数必须先定义再使用, 否则要用
(declare function-names) 提前声明, 下面代码是在 Clojure 的 REPL 中执行的user=> (defn say-hello-to "say hello to some" [name] (str "Hello, " name)) #'user/say-hello-to user=> (doc say-hello-to) ------------------------- user/say-hello-to ([name]) say hello to some nil user=>
提一下 Clojure 的命名规则, 变量和函数名用中划线连接的小写单词.
defn- 定义的函数是私有, 只对当前名字空间可见, 比如上面的 user 名字空间. 有点像 Python 的下划线变量或函数的可见性约定.Clojure 支持可变函数(VarArgs),
& 之后的参数在一个 list 中, 如(defn accumulate [first & others] (reduce #(+ %1 %2) first others))
(println (accumulate 1 2 3 4 5 6)) ;-> 21
函数可包含多个参数及对应的方法体, 以此来实现默认参数或重载
(defn sayHello ([] (sayHello "World")) ; 相当于定义了两个函数了, 瞧这里正调用第二个函数 ([name] (str "Hello " name)) )
(println (sayHello)) ; -> Hello World (println (sayHello "Yanbin")) ; -> Hello Yanbin
匿名函数已在第一篇中讲过了, 它其实就是一个 Lambda 表达式了, #(...), 参数直接用 %(%1), %2, %3 .... 了. Clojure 的这种方式的匿名函数还是很方便的.
comp 可以把多个函数以管道形式组合起来, 如(defn times2 [n] (* n 2)) (defn minus3 [n] (- n 3))
(def my-composition (comp minus3 times2)) ;注释 用的 def, my-composition 是一个函数类型的变量
(println (my-composition 4)) ;-> 4 * 2 -3 = 5
(defn my-composition1 [n] (-> (times2 n) minus3)) (println (my-composition1 4)) ; 同样的效果 4 * 2 - 3 = 5
complement 接受函数为参数求补, partial 给旧的函数制定一个初始值. 与 Ruby 的约定一样, Clojure 定义谓词型的函数使用问号, 像
(defn teenager? [age] (and (>= age 13) (< age 20))) (teenager? 15) ;-> true (teenager? 20) ;-> false
学习 Clojure 到现在基本能够体会到它的代码就是数据, 代码中常用的
() 和 [] 怎么变都是 List 和 Vector 的组合, 再多就是把 List 和 Map 加进去. 数据结构就是编程, 谁想到 Lisp 这么一种语言真是太有才了.发现一个有意思的事情, 下面的代码因为输入有误, 结果是 Cloujure 把结果都算出来了, 再告诉我最后多了一个括号, 可见它的执行机制是能匹配到括号就能行, 多了也不是大事. 可以试下
(def v 123))))))) 再多的括号都能为你把变量 v = 123 正确的定义出来.user=> (reduce #(+ %1 %2) [1 2 3 4 5])) 15 RuntimeException Unmatched delimiter: ) clojure.lang.Util.runtimeException (Util.java:221)
Java 互操作
Clojure 使用 Java 的东西可没有 Scala 那么简单, 主要了解几个宏
import, ., .., .?., 以及 doto 函数. Clojure 为同一个操作准备了多种方式, 看似灵活, 其实让人更凌乱. 使用 (doc import), (doc doto) 这样来查看使用方法. 大概用代码罗列一下:(import ;import 是一个变参函数,它的每一个元素又是一个列表 '(java.util Calendar GregorianCalendar) //列表第一个元素是包名,其他是各个类 '(javax.swing JFrame JLabel))
Calendar/APRIL ;->3 如果 Calendar 已导入, 或 (. java.util.Calendar APRIL) 访问类常量 (Math/pow 2 4) ; 或 (. Math pow 2 4),相当于把 . 当作函数名,Math pow 2 4 分别是它的参数
(def calendar (GregorianCalendar. 2008 Calendar/APRIL 16)) ;或 ..(new GregorianCalendar 2008 3 16) (. calendar add Calendar/MONTH 2) (.get calendar Calendar/MONTH) ; 两种方式调用实例方法
真正要使用的时候再回过头来看吧, 主要有两点比较新颖: 1)
.. 能把方法串接起来 2) doto 可用来调用一个对象的多个方法, 方法不要求 return this 也能链接:(.. calendar getTimeZone getDisplayName) ;相当于 (. (. calendar getTimeZone) getDisplayName), .?. 短路操作,可容错 (doto calendar ; doto 在每次调用下面的方法都会返回它的第一个参数 calendar (.set Calendar/YEAR 1981) (.set Calendar/MONTH Calendar/AUGUST))
所有的 Clojure 方法都实现了
java.lang.Runnable 接口. Clojure 的异常都是运行时异常. 如果要捕获从 Java 代码抛出的检测异常需要用到 try, catch, finally, 和 throw 那些被称作 special forms 的东西.流程控制
条件处理, special formif, 宏 when, when-not, 能绑定 binding 变量的宏 if-let 和 when-let, 还有相当于 switch/case 语句的 condp 和 cond 两个宏.do 可以用来包裹多个操作, 方便在单个条件中执行多条语句. 和 Scala 一样, Clojure 的条件语句是有返回值的. 下面一起进到代码里去理解(if true ;if 可有三个参数, 第一个为条件, 第二个为成立时表达式, 第三个为可选的不成立时的表达式
(println "play")
(do (println "work") ; 有三个条件时, 相当于 ? : 三无操作符
(println "sleep")))
(when is-weekend (println "play")) ;只在条件成立时干什么
(when-not is-weekend (println "work") (println "sleep")
(if-let [name (first waiting-line)] ;如果 waiting-line 为空时, first 返回 nil 就代表 false
(println name "is next")
(println "no waiting"))
(when-let [head (first coll)] (print head))
(condp = value ;第一个参数谓词 =, 第二个参数要比较的值 value, 后面任意多个值-表达式对, 最后为 default
1 "one"
2 "two"
(str "unexpected value, " value)) ;这个是 default
(cond ;与 condp 不同的是, 它可以提供不同的比较方式
(instance? String temperature) :invalide temperature"
(<= temperature 0) "freezing"
(>= temperature 100) "boiling"
true "neither"))condp 和 cond 匹配不到条件就会抛出 IllegalArgumentException, 每个分支自带 break 功能.循环, 用宏
dotimes 和 while(dotimes [card-number 3] ;card-number 是一个本地 binding, 如果用不上就写成 (dotimes [_ 3]), 和 Scala 一样 (print card-number)) ;-> 012
(while true (print "."))
宏
for, doseq 和 special form loop, 它们可以用 :when 或 :while 进行过滤, 例子:(def cols "ABCD") (def rows (range 1 4))Clojure 的循环与 Java 的 foreach 写法有点类似.
(dorun ;dorun 不让 for 循环返回集合, 没有 dorun 的话 for 有返回值 (nil nil nil nil nil nil) (for [col cols :when (not= col \B) ;\B 语法糖表示字符 B row rows :while (< row 3)] ;多重循环只要写在这个 Vector 中就行,单循环为 (for [col cols] (prinltn col)) (println (str col row))))
(doseq [col cols :when (not= col \B) row rows :while (< row 3)] (println (str col row)))
往下是递归, Clojure 和 Java 一样也是不支持尾递归优化的,
loop/recur 组合可把一个看似递归的调用变成一个迭代, 这是做了尾递归优化该做的事情了.已经不想再往下写了, 先了解这些 Clojure 的基础知识了, 剩下的还有一本 "Clojure IN ACTION" 这本书要细细的品读了.