## 函數定義
`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調用就要快很多了。