高階関数とラムダ
高階関数
高階関数はパラメータとして関数を取るか、関数を返す関数です。このような機能の良い例が lock() のような関数です。これは lock オブジェクトと関数を受け取り、 lock をかけ、関数を実行して lock を解放します。
- fun <T> lock(lock: Lock, body: () -> T): T {
lock.lock()
try {
return body()
}
finally {
lock.unlock()
}
}
それでは、上記のコードを見てみましょう。 body は関数型: () -> T を持ちます。つまり、パラメータを取らず、型 T の値を返す関数と考えられます。 body は lock によって保護されながら、 try ブロック内で呼び出され、その結果は lock() 関数によって返されます。
lock() を呼び出したい場合は、引数として別の関数を渡すことができます(関数の参照を参照してください):
- fun toBeSynchronized() = sharedResource.operation()
val result = lock(lock, ::toBeSynchronized)
別手段として、多くの場合、より便利な方法はラムダ式を渡すことです:
- val result = lock(lock, { sharedResource.operation() })
ラムダ式は以下でより詳細に説明しますが、このセクションを継続したいので、今は簡単な概要を見てみましょう:
- ?ラムダ式は、常に中括弧で囲まれています
?そのパラメータは、(もしあれば) -> の前で宣言されます(パラメータの型を省略してもかまいません)
?本体が -> に続きます(存在する場合)
Kotlinでは、関数の最後のパラメータが関数である場合、そのパラメータは括弧の外に指定することができるという慣習があります:
- lock (lock) {
sharedResource.operation()
}
別の例で高階関数が map() になっています:
- 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
}
この関数を次のように呼び出すことができます。
- val doubled = ints.map { it -> it * 2 }
ラムダがその呼び出しに唯一の引数である場合、呼び出しの括弧を完全に省略することができることに注意してください。
it : 単一パラメータの暗黙の名前
もう一つの有用な慣習は、関数リテラルがパラメータを1つだけ持つ場合、その宣言を( -> と一緒に)省略してもよいということです。その場合、その名前は、 it になります。
- ints.map { it * 2 }
これらの慣習により、 LINQスタイル のコードを書くことができます:
- strings.filter { it.length == 5 }.sortBy { it }.map { it.toUpperCase() }
インライン関数
場合によっては、インライン関数の使用は高階関数のパフォーマンスの向上に有効です。
ラムダ式と無名関数
ラムダ式や無名関数は「関数リテラル」です。すなわち、その関数は宣言されたのではなく、表現としてすぐに渡されたということです。次の例を考えてみます:
- max(strings, { a, b -> a.length < b.length })
関数 max は高階関数です。すなわち2番目の引数として関数値をとります。この2番目の引数はそれ自体が関数である式、すなわち関数リテラルです。関数としては、次と等価です:
- fun compare(a: String, b: String): Boolean = a.length < b.length
関数型
関数がパラメータとして別の関数を受け入れられるようにするために、そのパラメータの関数型を指定する必要があります。たとえば、次のように前述の関数 max が定義されているとします:
- 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 型、すなわち2つの T 型のパラメータをとり、前者が後者より小さければ Boolean : true を返す関数です。
本体の4行目では、 less は関数として使用されています。つまり、型 T の2つの引数を渡すことによってその関数は呼び出されました。
関数型は前述の通り、または各パラメータの意味をドキュメント化する場合は、名前付きパラメータを使用することがあります。
- val compare: (x: T, y: T) -> Int = ...
ラムダ式の構文
ラムダ式、つまり関数型リテラルの完全な構文形式は、次のとおりです。
- val sum = { x: Int, y: Int -> x + y }
ラムダ式は常に中括弧で囲まれ、完全な構文形式のパラメータ宣言はカッコ内にあり、型注釈を持つことができ、本体は -> 記号の後に置かれます。必須ではない注釈をすべて省略した場合、残っているものは次のようになります:
- val sum: (Int, Int) -> Int = { x, y -> x + y }
ラムダ式がパラメータを1つだけ持っていることはよくあることです。もしKotlinが署名自体を理解することができれば、唯一のパラメータ宣言を省略することができ、暗黙のうちにそれを it という名で宣言します。
- ints.filter { it > 0 } // このリテラルは '(it: Int) -> Boolean' 型
関数が最後のパラメータとして別の関数を取る場合は、ラムダ式の引数は括弧で囲まれた引数リストの外に渡すことができることに注意してください。callSuffixのための文法を参照してください。
無名関数
上記のラムダ式の構文から一つ欠落しているのは、関数の戻り値の型を指定する機能です。ほとんどの場合は、戻り型を自動的に推論することができるので不要です。しかし、それを明示的に指定する必要がある場合、別の構文を使用することができます。_無名関数_です。
- fun(x: Int, y: Int): Int = x + y
無名関数は、その名が省略されていることを除いて、通常の関数の宣言と非常のよく似ています。その本体は、式(上記のように)、またはブロックのいずれかになります:
- fun(x: Int, y: Int): Int {
return x + y
}
パラメータおよび戻り型は、それらが文脈から推測することができ、パラメータの種類を省略することができる場合を除き、通常の関数と同じ方法で指定されます。
- ints.filter(fun(item) = item > 0)
無名関数の戻り値の型推論は普通の関数のように動作します:戻り値の型は式本体と無名関数のために自動的に推論され、ブロック本体で無名関数のために明示的に指定され(または Unit とされ)ます。
無名関数のパラメータは、常にかっこ内に渡されることに注意してください。括弧の外の関数を残すことができるように、速記構文はラムダ式に対してのみ機能します。
ラムダ式と無名関数の間のもう一つの違いは、非局所的なリターンの動作です。ラベルなしの return 文は、常に fun キーワードで宣言された関数から返されます。これは、ラムダ式の内側からの return は囲んでいる関数から返される一方で、無名関数の内部 return は無名関数自体から返されることを意味します。
クロージャ
ラムダ式や無名関数(ならびにローカル関数やオブジェクト式)は、その クロージャ 、すなわち、外側のスコープで宣言された変数にアクセスすることができます。Javaとは異なり、クロージャに取り込まれた変数を変更することができます。
- var sum = 0
ints.filter { it > 0 }.forEach {
sum += it
}
print(sum)
レシーバ付き関数リテラル
Kotlinは、指定された レシーバ・オブジェクト と関数リテラルを呼び出す機能を提供します。リテラル関数の本体内では、任意の追加の修飾子なしでそのレシーバオブジェクトのメソッドを呼び出すことができます。これは、関数の本体内にあるレシーバオブジェクトのメンバにアクセスすることを可能にする拡張機能に似ています。それらの使用法の最も重要な例の一つは、型安全のGroovyスタイルのビルダーです。
このような関数リテラルの型は、レシーバを持つ関数型です:
- sum : Int.(other: Int) -> Int
それはレシーバオブジェクトのメソッドであるかのように関数リテラルを呼び出すことができます。
- 1.sum(2)
無名関数の構文は、直接関数リテラルのレシーバの種類を指定することができます。レシーバを持つ関数型の変数を宣言し、後でそれを使用する必要がある場合に役立ちます。
- val sum = fun Int.(other: Int): Int = this + other
レシーバ型は文脈から推測することができる場合、ラムダ式は、レシーバ関数リテラルとして使用することができます。
- class HTML {
fun body() { ... }
}
fun html(init: HTML.() -> Unit): HTML {
val html = HTML() // レシーバオブジェクトを生成
html.init() // そのレシーバオブジェクトをラムダに渡す
return html
}
html { // レシーバ付きラムダがここから始まる
body() // レシーバオブジェクトのメソッドを呼んでいる
}