## 函数定义
`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调用就要快很多了。