# 高階函數與 Lambda
## 高階函數
所謂高階函數就是有函數作為參數的函數,或是返回一個函數。這種函數的一個好的例子就是 `lock()`,帶有一個 lock 對象和一個函數,獲得 lock,運行函數然后釋放這個 lock:
``` kotlin
fun <T> lock(lock: Lock, body: () -> T): T {
lock.lock()
try {
return body()
}
finally {
lock.unlock()
}
}
```
讓我們審視一下上面的代碼:`body` 有一個[函數類型](#function-types) `() -> T`,所以假定它是一個函數不帶參數并返回一個類型為 `T` 的值,它在 `try` 塊中被調用,又被 `lock` 所保護,而且它的結果通過 `lock()` 函數返回。
如果我們要調用 `lock()`,我們可以傳遞不同的函數作為實參給它(查看[函數引用](reflection.html#function-references)):
``` kotlin
fun toBeSynchronized() = sharedResource.operation()
val result = lock(lock, ::toBeSynchronized)
```
另外,一般更方便的方式是傳遞一個 [lambda 表達式](#lambda-expressions-and-anonymous-functions):
``` kotlin
val result = lock(lock, { sharedResource.operation() })
```
lambda 表達式在下面有更[詳細的描述](#lambda-expressions-and-anonymous-functions),但為了本節內容的連續性,讓我們概覽一下:
* 一個 lambda 表達式始終圍繞著花括號,
* 它的參數(如果有的話)在 `->` 的前面聲明(參數的類型可以被推斷出),
* 主體在 `->` 后面(如果有的話)
在 Kotlin 里面,有一個約定,如果最后那個函數參數是一個函數,那么那個函數可以在圓括號外面指定:
``` kotlin
lock (lock) {
sharedResource.operation()
}
```
另一個高階函數的例子應該是 `map()`:
``` kotlin
fun <T, R> List<T>.map(transform: (T) -> R): List<R> {
val result = arrayListOf<R>()
for (item in this)
result.add(transform(item))
return result
}
```
這個函數可以在下面調用:
``` kotlin
val doubled = ints.map { it -> it * 2 }
```
注意如果 lambda 是調用時僅有的實參,那么圓括號在調用時完全可以省略。
另一個很有幫助的約定是如果一個函數字面上只有一個參數,它的聲明可以省略(`->` 順道省略),并且它的名稱將是 `it`:
``` kotlin
ints.map { it * 2 }
```
這些約定允許書寫 [LINQ 風格](http://msdn.microsoft.com/en-us/library/bb308959.aspx)的代碼:
``` kotlin
strings.filter { it.length == 5 }.sortBy { it }.map { it.toUpperCase() }
```
## 內聯函數
有時使用[內聯函數](inline-functions.html)對提升高階函數的性能很有好處。
## lambda 表達式與匿名函數
lambda 表達式或才匿名函數是“函數字面值”,意即一個未經聲明函數,但被隱含地作為一個表達式傳遞。思考下面的示例:
``` kotlin
max(strings, { a, b -> a.length() < b.length() })
```
函數 `max` 是一個高階函數,意即它帶有一個函數值作為第二個實參。這第二個實參是一個自身為函數的表達式,意即一個函數字面值。作為一個函數它等價于
``` kotlin
fun compare(a: String, b: String): Boolean = a.length() < b.length()
```
### 函數類型
要一個函數作為參數接受另一個函數,我們得為那個參數指定一個函數類型。例如上述的函數 `max` 定義如下:
``` kotlin
fun <T> max(collection: Collection<T>, less: (T, T) -> Boolean): T? {
var max: T? = null
for (it in collection)
if (max == null || less(max, it))
max = it
return max
}
```
參數 `less` 的類型是 `(T, T) -> Boolean`,意即一個函數帶有兩個類型為 `T` 的參數并返回一個 `Boolean`:如果第一個比第二個小則為真。
主體第 4 行,`less` 作為一個函數使用:它通過傳遞兩個類型為 `T` 的實參來調用。
一個函數類型要像上面那樣書寫,或者如果你要記錄每個參數的意義的話,也可以有命名的參數。
``` kotlin
val compare: (x: T, y: T) -> Int = ...
```
### lambda 表達式語法
lambda 表達式完整的語法形式,即函數類型的文本,是像下面這樣的:
``` kotlin
val sum = { x: Int, y: Int -> x + y }
```
lambda 表達式始終以花括號包圍,完整語法形式中參數聲明在圓括號里面并擁有可選的注解,主體則在一個 `->` 符號后面。如果可選的注解我們全部不要,看上去就是這樣的:
``` kotlin
val sum: (Int, Int) -> Int = { x, y -> x + y }
```
lambda 表達式通常只有一個參數。如果 Kotlin 能推測它自身的簽名,就能允許我們不聲明這個僅有的參數,而是隱含地為我們把聲明它為 `it`:
``` kotlin
ints.filter { it > 0 } // 字面類型為 '(it: Int) -> Boolean'
```
注意如果一個函數帶了其它函數作為最后一個參數,那么這個 lambda 表達式實參可以在參數列表的括號外面傳遞。查看語法入門的[調用后綴](grammar.html#call-suffix)。
### 匿名函數
上面 lambda 表達式語法唯一不能展現的技能就是函數的返回類型。在大多數情況下并無此必要,因為返回類型可以自動推斷。然而,如果你需要明確地指定它,你可以使用一種替代語法:一個_匿名函數_。
``` kotlin
fun(x: Int, y: Int): Int = x + y
```
除非忽略它的名稱之外,匿名函數看上去十分像普通函數聲明。它的主體既可以是一個表達式(如上面所展示的一樣),又可以是一個塊:
``` kotlin
fun(x: Int, y: Int): Int {
return x + y
}
```
參數和返回類型的指定方式與普通函數一樣,除了在參數類型能從上下文推斷出時可以被省略之外:
``` kotlin
ints.filter(fun(item) = item > 0)
```
匿名函數的返回類型推斷如普通函數一樣:返回類型會自動地從表達式主體推斷出,而必須為帶有塊體的匿名函數明確地指出(或假定為 `Unit`)。
注意匿名函數參數始終在圓括號里面傳遞。允許離開函數圓括號外部的簡寫語法只針對 lambda 表達式。
一個其它不同于 lambda 表達式和匿名函數的是[非本地返回](inline-functions.html#non-local-returns)的行為。一個不帶標簽的 `return` 指令總是從 `func` 關鍵字聲明的地方返回。這個意思就是一個 lambda 表達式里的 `return` 將從函數閉包返回,而匿名 函數的 `return` 將從這個匿名函數自身返回。
### 閉包
一個 lambda 表達式或匿名函數(與[局部函數](functions.html#local-functions)和[對象表達式](object-declarations.html#object-expressions)一樣)可以訪問它的_閉包_,意即這個在外部作用域聲明的變量。與 Java 不同,這個在閉包內捕獲的變量可以被修改:
``` kotlin
var sum = 0
ints.filter { it > 0 }.forEach {
sum += it
}
print(sum)
```
### 帶接受者的函數字面量
Kotlin 提供了調用指定了_接受者對象_的函數字面量的功能。在函數字面量的主體中,你可以在接受者對象上調用方法而無須什么附加的限定符。這與擴展函數類似,允許你在函數主體內部訪問接受者對象的成員。它們使用的一個很重要的例子是[類型安全的 Groovy 風格構建器](type-safe-builders.html)
這樣的函數字面量的類型為一個帶有接受者的函數類型:
``` kotlin
sum : Int.(other: Int) -> Int
```
這個函數字面量可以在接受者對象上像方法一樣被調用:
``` kotlin
1.sum(2)
```
匿名函數語法允許你直接指定一個函數字面量的接受者類型。如果你需要聲明一個帶有接受者的函數類型的變量并在以后使用它,這會很有用。
``` kotlin
val sum = fun Int.(other: Int): Int = this + other
```
在接受者類型能夠從上下文推斷出來時,lambda 表達式可以像帶接受者的函數字面量一樣使用。
``` kotlin
class HTML {
fun body() { ... }
}
fun html(init: HTML.() -> Unit): HTML {
val html = HTML() // 創建接受者對象
html.init() // 傳遞接受者對象到 lambda
return html
}
html { // 帶接受者的 lambda
body() // 在接受者對象上調用一個方法
}
```