元数据 元数据提供了在必要时为值添加标识的 一种手段。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 (def untrusted (with-meta {:command "delete-table" :subject "users" } {:safe false :io true })) (def untrusted {:command "delete-table" :subject "users" }) untrusted (meta untrusted) (def trusted {:command "delete-table" :subject "users" }) (= trusted untrusted) (def untrusted2 (hash-map :command "delete-table" :subject "users" )) (meta untrusted2) (def still-untrusted (assoc untrusted :complete? false )) still-untrusted (meta still-untrusted)
函数与宏也可以在定义中包含元数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 (defn testing-meta [] (println "Hello from meta!" )) (meta testing-meta) (meta (var testing-meta))
Java 类型提示 调用Java方法时,需要通过类找到方法的实现。但是,Clojure 是动态语言,变量类型只有在运行时才知道。可以使用读取器宏^symbol
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 (set! *warn-on-reflection* true ) (defn string-length [x] (.length x)) (time (reduce + (map string-length (repeat 10000 "12345" )))) (defn fast-string-length [ x] (.length x)) (time (reduce + (map fast-string-length (repeat 10000 "12345" )))) (meta (first (first (:arglists (meta #'fast-string-length))))) => {:tag String}
Clojure 编译器在类型推导上相当智能,所有核心函数已经在必要时做了类型提示,所以不经常需要采用类型提示。
原始类型没有可读的类名可提供引用,Clojure 为所有原始类型和原始类型数组定义了别名:只需要使用^byte
这样的类型提示表示原始类型,^bytes
这样的复数形式表示原始类型数组。
Java 异常处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 (defn average [numbers] (let [total (apply + numbers)] (/ total (count numbers)))) (average []) (defn safe-average [numbers] (let [total (apply + numbers)] (try (/ total (count numbers)) (catch ArithmeticException e (println "Divided by zero!" ) 0 )))) (safe-average [])
如果有表达式产生异常,则 根据异常类型执行对应的 catch
子句,返回该子句的值。可选的finally
子句总会被执行,用于保证必须的副作用,但不返回任何数值。
1 2 3 4 5 6 7 (try (print "Attempting division... " ) (/ 1 0 ) (finally (println "done." ))) Attempting division... done.
需要注意catch
子句的顺序。
1 2 3 4 5 6 7 8 9 10 (try (print "Attempting division... " ) (/ 1 0 ) (catch RuntimeException e "Runtime exception!" ) (catch ArithmeticException e "DIVIDE BY ZERO!" ) (catch Throwable e "Unknown exception encountered!" ) (finally (println "done." )))
异常可以使用throw
形式抛出,在希望抛出的场合可以使用: (throw (Exception. "this is an error"))
。
函数 先决和后置条件 在执行函数主体之前运行的检查由:pre
指定,称为先决条件
。:post
键指定的条件称为后置条件
,条件中的%
指的就是函数的返回值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 (defn item-total [price quantity discount-percentage] {:pre [(> price 0 ) (> quantity 0 )] :post [(> % 0 )]} (->> (/ discount-percentage 100 ) (- 1 ) (* price quantity) float)) (item-total 100 2 0 ) (item-total 100 2 10 ) (item-total 100 -2 10 ) (item-total 100 2 110 )
重载(多种参数数量) 1 2 3 4 5 (defn total-cost ([item-cost number-of-items] (* item-cost number-of-items)) ([item-cost] (total-cost item-cost 1 )))
可以从某种参数数量的函数中调用其他参数数量的版本。
可变参数函数 在 Clojure 中使用&
符号实现变长参数功能。
1 2 (defn total-all-numbers [& numbers] (apply + numbers))
可变参数函数中有一些不可变的参数。可变参数中的必要参数数量至少要与最长的固定参数相同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 (defn many-arities ([] 0 ) ([a] 1 ) ([a b c] 3 ) ([a b c & more] "variadic" )) (many-arities ) (many-arities "one argument" ) (many-arities "two" "arguments" ) (many-arities "three" "argu-" "ments" ) (many-arities "many" "more" "argu-" "ments" )
高阶参数 every? 接受一个返回布尔值的函数(判定函数)和一个序列
1 2 3 4 5 (def bools [true true true false false ]) (every? true ? bools) (every? even? '(2 4 6 ))
some 接受一个判定和一个序列,返回获得的第一个逻辑true值,如果调用都不返回逻辑 true,则返回 nil。
1 2 (some (fn [p] (= "rob" p)) ["kyle" "siva" "rob" "celeste" ])
constantly 接受一个值 v,返回一个可变参数函数,这个函数不管输入的参数为何,总是返回相同的值 v。
1 2 3 4 5 6 7 8 (def two (constantly 2 )) (two 1 ) (two :a :b :c )
complement 接受一个函数作为参数,返回与原始函数参数数量相同、完成相同工作但返回逻辑相反值的函数。
1 2 3 4 5 6 7 8 9 10 11 12 (defn greater? [x y] (> x y)) (greater? 10 5 ) (greater? 10 20 ) (def smaller? (complement greater?)) (smaller? 10 5 ) (smaller? 10 20 )
comp 接受多个函数并返回由哪些函数组合而成的新函数。计算从右到左进行,新函数将其参数应用于原始组成函数中最右侧的一个,然后将结果应用到它左边的函数,直到所有函数都被调用。
1 2 3 4 5 6 7 (def opp-zero-str (comp str not zero?)) (opp-zero-str 0 ) (opp-zero-str 1 )
partial 接受函数 f 以及 f 的几个参数,然后partial
返回一个新函数,接受 f 的其余参数。当以余下的参数调用新函数时,它以全部参数调用原始函数 f。
1 2 3 4 5 6 7 (defn above-threshold? [threshold number] (> number threshold)) (filter (fn [x] (above-threshold? 5 x)) [1 2 3 4 5 6 7 8 9 ]) (filter (partial above-threshold? 5 ) [1 2 3 4 5 6 7 8 9 ])
memoize 内存化可以避免函数为已处理过的参数计算结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 (defn slow-calc [n m] (Thread/sleep 1000 ) (* n m)) (time (slow-calc 5 7 )) (def fast-calc (memoize slow-calc)) (time (fast-calc 5 7 )) (time (fast-calc 5 7 ))
注意:memoize
缓存没有限定大小,因而会不停缓存输入和结果。因此该函数只应该用于少量可能输入的函数,否则最终会把内存耗尽。更高级的内存化功能,可以使用clojure.core.memoize
库。
匿名函数 匿名函数使用fn
创建。
1 2 3 (defn sorter-using [ordering-fn] (fn [collection] (sort-by ordering-fn collection)))
同时可以使用#()
创建一个匿名函数。%
表示一个参数,如果超过一个参数,则可以使用%1
、%2
…还可以使用%&
表示除明确引用的%&
参数之外的参数。
1 2 3 4 5 6 7 8 (# (vector %&) 1 2 3 4 5 ) (# (vector % %&) 1 2 3 4 5 ) (# (vector %1 %2 %&) 1 2 3 4 5 ) (# (vector %1 %2 %&) 1 2 )