関数リテラルまとめ

kotlinlang.org

関数リテラル

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)