ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
## 函数定义 `defn` 宏用来定义一个函数。它的参数包括一个函数名字,一个可选的注释字符串,参数列表,然后一个方法体。而函数的返回值则是方法体里面最后一个表达式的值。所有的函数都会返回一个值, 只是有的返回的值是nil。看例子: ``` (defn parting "returns a String parting" [name] (str "Goodbye, " name)) ; concatenation (println (parting "Mark")) ; -> Goodbye, Mark ``` 函数必须先定义再使用。有时候可能做不到, 比如两个方法项目调用,clojure采用了和C语言里面类似的做法: declare, 看例子: ``` (declare <em>function-names</em>) ``` 通过宏 `defn-` 定义的函数是私有的. 这意味着它们只在定义它们的名字空间里面可见. 其它一些类似定义私有函数/宏的还有: `defmacro-` 和 `defstruct-` (在 `clojure.contrib.def` 里面)。 函数的参数个数可以是不定的。可选的那些参数必须放在最后面(这一点跟其它语言是一样的), 你可以通过加个&符号把它们收集到一个list里面去Functions can take a variable number of parameters. Optional parameters must appear at the end. They are gathered into a list by adding an ampersand and a name for the list at the end of the parameter list. ``` (defn power [base & exponents] ; Using java.lang.Math static method pow. (reduce #(Math/pow %1 %2) base exponents)) (power 2 3 4) ; 2 to the 3rd = 8; 8 to the 4th = 4096 ``` 函数定义可以包含多个参数列表以及对应的方法体。每个参数列表必须包含不同个数的参数。这通常用来给一些参数指定默认值。看例子: ``` (defn parting "returns a String parting in a given language" ([] (parting "World")) ([name] (parting name "en")) ([name language] ; condp is similar to a case statement in other languages. ; It is described in more detail later. ; It is used here to take different actions based on whether the ; parameter "language" is set to "en", "es" or something else. (condp = language "en" (str "Goodbye, " name) "es" (str "Adios, " name) (throw (IllegalArgumentException. (str "unsupported language " language)))))) (println (parting)) ; -> Goodbye, World (println (parting "Mark")) ; -> Goodbye, Mark (println (parting "Mark" "es")) ; -> Adios, Mark (println (parting "Mark", "xy")) ; -> java.lang.IllegalArgumentException: unsupported language xy ``` 匿名函数是没有名字的。他们通常被当作参数传递给其他有名函数(相对于匿名函数)。匿名函数对于那些只在一个地方使用的函数比较有用。下面是定义匿名函数的两种方法: ``` (def years [1940 1944 1961 1985 1987]) (filter (fn [year] (even? year)) years) ; long way w/ named arguments -> (1940 1944) (filter #(even? %) years) ; short way where % refers to the argument ``` 通过 `fn` 定义的匿名函数可以包含任意个数的表达式; 而通过 `#(...)` , 定义的匿名函数则只能包含一个表达式,如果你想包含多个表达式,那么把它用 `do` 包起来。如果只有一个参数, 那么你可以通过 `%` 来引用它; 如果有多个参数, 那么可以通过 `%1` , `%2` 等等来引用。 看例子: ``` (defn pair-test [test-fn n1 n2] (if (test-fn n1 n2) "pass" "fail")) ; Use a test-fn that determines whether ; the sum of its two arguments is an even number. (println (pair-test #(even? (+ %1 %2)) 3 5)) ; -> pass ``` Java里面的方法可以根据参数的类型来进行重载。而Clojure里面则只能根据参数的个数来进行重载。不过Clojure里面的multimethods技术可以实现任意 类型的重载。 宏 `defmulti` 和 `defmethod` 经常被用在一起来定义 multimethod. 宏 `defmulti` 的参数包括一个方法名以及一个dispatch函数,这个dispatch函数的返回值会被用来选择到底调用哪个重载的函数。宏 `defmethod` 的参数则包括方法名,dispatch的值, 参数列表以及方法体。一个特殊的dispatch值 `:default` 是用来表示默认情况的 — 即如果其它的dispatch值都不匹配的话,那么就调用这个方法。 `defmethod` 多定义的名字一样的方法,它们的参数个数必须一样。传给multimethod的参数会传给dipatch函数的。 下面是一个用multimethod来实现基于参数的类型来进行重载的例子: ``` (defmulti what-am-i class) ; class is the dispatch function (defmethod what-am-i Number [arg] (println arg "is a Number")) (defmethod what-am-i String [arg] (println arg "is a String")) (defmethod what-am-i :default [arg] (println arg "is something else")) (what-am-i 19) ; -> 19 is a Number (what-am-i "Hello") ; -> Hello is a String (what-am-i true) ; -> true is something else ``` 因为dispatch函数可以是任意一个函数,所以你也可以写你自己的dispatch函数。比如一个自定义的dispatch函数可以会根据一个东西的尺寸大小来返回 `:small` , `:medium` 以及 `:large` 。然后对应每种尺寸有一个方法。 下划线可以用来作为参数占位符 ?– 如果你不要使用这个参数的话。这个特性在回调函数里面比较有用, 因为回调函数的设计者通常想把尽可能多的信息给你, 而你通常可能只需要其中的一部分。看例子: ``` (defn callback1 [n1 n2 n3] (+ n1 n2 n3)) ; uses all three arguments (defn callback2 [n1 _ n3] (+ n1 n3)) ; only uses 1st & 3rd arguments (defn caller [callback value] (callback (+ value 1) (+ value 2) (+ value 3))) (caller callback1 10) ; 11 + 12 + 13 -> 36 (caller callback2 10) ; 11 + 13 -> 24 ``` `complement` 函数接受一个函数作为参数,如果这个参数返回值是true, 那么它就返回false, 相当于一个取反的操作。看例子: ``` (defn teenager? [age] (and (>= age 13) (< age 20))) (def non-teen? (complement teenager?)) (println (non-teen? 47)) ; -> true ``` `comp` 把任意多个函数组合成一个,前面一个函数的返回值作为后面一个函数的参数。 **调用的顺序是从右到左(注意不是从左到右)** 看例子: ``` (defn times2 [n] (* n 2)) (defn minus3 [n] (- n 3)) ; Note the use of def instead of defn because comp returns ; a function that is then bound to "my-composition". (def my-composition (comp minus3 times2)) (my-composition 4) ; 4*2 - 3 -> 5 ``` `partial` 函数创建一个新的函数 — 通过给旧的函数制定一个初始值, 然后再调用原来的函数。比如 `*` 是一个可以接受多个参数的函数,它的作用就是计算它们的乘积,如果我们想要一个新的函数,使的返回结果始终是乘积的2倍,我们可以这样做: ``` ; Note the use of def instead of defn because partial returns ; a function that is then bound to "times2". (def times2 (partial * 2)) (times2 3 4) ; 2 * 3 * 4 -> 24 ``` 下面是一个使用 `map` 和 `partial` 的有趣的例子. ``` (defn- polynomial "computes the value of a polynomial with the given coefficients for a given value x" [coefs x] ; For example, if coefs contains 3 values then exponents is (2 1 0). (let [exponents (reverse (range (count coefs)))] ; Multiply each coefficient by x raised to the corresponding exponent ; and sum those results. ; coefs go into %1 and exponents go into %2. (apply + (map #(* %1 (Math/pow x %2)) coefs exponents)))) (defn- derivative "computes the value of the derivative of a polynomial with the given coefficients for a given value x" [coefs x] ; The coefficients of the derivative function are obtained by ; multiplying all but the last coefficient by its corresponding exponent. ; The extra exponent will be ignored. (let [exponents (reverse (range (count coefs))) derivative-coefs (map #(* %1 %2) (butlast coefs) exponents)] (polynomial derivative-coefs x))) (def f (partial polynomial [2 1 3])) ; 2x^2 + x + 3 (def f-prime (partial derivative [2 1 3])) ; 4x + 1 (println "f(2) =" (f 2)) ; -> 13.0 (println "f'(2) =" (f-prime 2)) ; -> 9.0 ``` 下面是另外一种做法 (Francesco Strino建议的). %1 = a, %2 = b, result is ax + b %1 = ax + b, %2 = c, result is (ax + b)x + c = ax^2 + bx + c ``` (defn- polynomial "computes the value of a polynomial with the given coefficients for a given value x" [coefs x] (reduce #(+ (* x %1) %2) coefs)) ``` `memoize` 函数接受一个参数,它的作用就是给原来的函数加一个缓存,所以如果同样的参数被调用了两次, 那么它就直接从缓存里面返回缓存了的结果,以提高效率, 但是当然它会需要更多的内存。(其实也只有函数式编程里面能用这个技术, 因为函数没有side-effect, 多次调用的结果保证是一样的) `time` 宏可以看成一个wrapper函数, 它会打印被它包起来的函数的执行时间,并且返回这个函数的返回值。看下面例子里面是怎么用的。 下面的例子演示在多项式的的计算里面使用memoize: ``` ; Note the use of def instead of defn because memoize returns ; a function that is then bound to "memo-f". (def memo-f (memoize f)) (println "priming call") (time (f 2)) (println "without memoization") ; Note the use of an underscore for the binding that isn't used. (dotimes [_ 3] (time (f 2))) (println "with memoization") (dotimes [_ 3] (time (memo-f 2))) ``` 上面代码的输出是这样的: ``` priming call "Elapsed time: 4.128 msecs" without memoization "Elapsed time: 0.172 msecs" "Elapsed time: 0.365 msecs" "Elapsed time: 0.19 msecs" with memoization "Elapsed time: 0.241 msecs" "Elapsed time: 0.033 msecs" "Elapsed time: 0.019 msecs" ``` 从上面的输出我们可以看到好几个东西。首先第一个方法调用比其它的都要长很多。– 其实这和用不用memonize没有什么关系。第一个memoize调用所花的时间也要比其他memoize调用花的时间要长, 因为要操作缓存,其它的memoize调用就要快很多了。