関数リテラルまとめ
関数リテラル
Kotlinは関数リテラルとして以下が用意されている
- ラムダ式
- 匿名関数
特徴
関数リテラルは、式として渡されるが、値として振る舞う事ができる 。
以下の例を考える
max(strings, { a, b -> a.length < b.length })
関数max
は、高階関数であり、第2引数に関数値を受け取る。
この第二引数は関数としての式であり、関数リテラルと呼ばれている。
これは以下の名前付き関数と同等である。
fun compare(a: String, b: String): Boolean = a.length < b.lenbth
ラムダ式構文
Lambda expression syntax
val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
- ラムダ式は、常に波括弧に囲まれる
- パラメーター宣言部 (
->
の前) - 本体 (
->
の後)
- パラメーター宣言部 (
- ラムダの返り値の型は基本
Unit
であり、最後の式が返り値の型として扱われる
パラメーターを宣言した場合は、型アノテーションを省略できる。
val sum = { x: Int, y: Int -> x + y }
トレーリングラムダを渡す
Passing trailing lambdas
※ trail A<軽い物など>を引きずる
Kotlinでは、簡易として、最後のパラメーターが関数の場合は、カッコの外側にラムダ式を配置することができる。
val product = items.fold(1) { acc, e -> acc * e }
そのような構文を trailing lambda と呼ぶ。
ラムダ式がただ一つの引数の場合は、カッコを省略することができる。
run { println("...") }
it: 暗黙のシングルパラメーター名
ラムダ式が、ただ一つのパラメーターを保つ場合は、
暗黙的にit
が宣言される。
この際、パラメーターの宣言と->
の宣言は許可されない。
ints.filter { it > 0 } // このリテラルの型は、'(it: Int) -> Boolean
ラムダ式から値を返す
Returning a value from a lambda expression
限定返り構文(the qualified return syntax)を使ってラムダからの値を明示的に返すことができるが、 最後の式の値が暗黙の返り値となる。
以下の2つのスニペットは同一である。
ints.filter { val shouldFilter = it > 0 shouldFilter }
ints.filter { val shouldFilter = it > 0 return@filter shouldFilter }
トレーリングラムダと合わせることで、LINQ-Styleコードを取ることができる
strings.filter { it.length > 5 }.sortedBy { it }.map { it.uppercase() }
未使用変数のためのアンダースコア
Underscore for unsued variables
map.forEach { _, value -> println("$value!") }
ラムダ内部の分割代入
Destructing in lambdas
https://kotlinlang.org/docs/destructuring-declarations.html#destructuring-in-lambdas
ラムダパラメーターに対して、分割宣言構文(destructing declarations syntax)を使うことができる。
Pair
タイプ(もしくは、Map.Entry
型やその他の適切なcomponentN
functionsを持つ型)をラムダのパラメーターとして持つ場合は、
複数のパラメーターに分割代入することができる。
map.mapValues { entry -> ${entry.value}" }
上記は、複数のパラメーターに分割できる。
map.mapValues { (key, value) -> "$value" }
匿名関数
匿名関数を使えば、返り値を指定することができる。 (ただし殆どの場合において、不要である。)
fun(x: Int, y: Int): Int = x + y
匿名関数は、通常の関数ととても似ているが、名前が省略されている。 その本体は、式(上記の例)もしくは、ブロックを指定することができる。
fun(x: Int, y: Int): Int = { return x + y } // 代入は、ブロックでのみ有効 var sum = 0 ints.forEach(fun(v) { sum += v } // 式内部では、代入は不可 // Assignments are not expressions, and only expressions are allowed in this context // ints.forEach(fun(v) = sum += v)
パラメーターと返り値の型は同時に宣言することができるが、推測できる場合は、排除できる。
ints.filter(fun(item) = item > 0)
NOTE: 匿名関数は、トレーリングラムダ式のように、外部に配置することはできない。
ラムダ式と匿名関数のもう一つの違いは、ローカルでない返り値の振る舞いである。
ラベルの無いreturn
文は、常にfun
キーワードを使って宣言された関数から返される。
つまり、
ラムダ式内部のreturn
は、内含された関数(the enclosing function)から返されるのに対して、
匿名関数内部のreturn
は、匿名関数から返される。
Closures
ラムダ式と匿名関数(ローカル関数かつオブジェクト式)は、そのクロージャーにアクセスすることができる。
※ クロージャー: 外側のスコープに宣言された変数を含む環境
クロージャー内部でキャプチャーされた変数は、ラムダ式内部で編集することができる。
var sum = 0 ints.filter { it > 0 }.forEach { sum += it } print(sum)
匿名関数を使った場合
var sum = 0 ints.filter(fun(v) = v > 0).forEach(fun(v) { sum += v }) print(sum)
- 匿名関数本体に式を渡した場合は、代入はできない
// NG ints.forEach(fun(v) = sum += v)