[毎日Kotlin] Day5.Lambdas(ラムダ式)

2018.01.17

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

毎日Kotlinシリーズです。

このシリーズを初めての方はこちらです。「毎日Kotlin」はじめました | Developers.IO

問題

コレクション(リスト)の中に偶数があるかどうかをチェックする関数を作ってみよう。

Lambdas | Try Kotlin

Kotlin supports a functional style of programming. Read about higher-order functions and function literals (lambdas) in Kotlin.

Pass a lambda to any function to check if the collection contains an even number. The function any gets a predicate as an argument and returns true if there is at least one element satisfying the predicate.

fun containsEven(collection: Collection<Int>): Boolean = collection.any { TODO() }

補足、anyは標準ライブラリーで提供されており、定義は以下の通り。

any/_Collections.kt at 1.2.0 · JetBrains/kotlin

//何か一つでもpredicateの条件と一致しているものがあったら、return true をするもの
public inline fun <T> Iterable<T>.any(predicate: (T) -> Boolean): Boolean {
    if (this is Collection && isEmpty()) return false
    for (element in this) if (predicate(element)) return true
    return false
}

狙い

ここで考えて欲しい問題の意図はなんだろうか?

JavaでもJava8が使えるようになりました。ラムダ式はAndroidでも頻繁に使うことになるので、ぜひ一度やっていただきたい問題です。コールバックやDSLなどに幅広い用途で使用されています。読みやすさの反面、ラムダ式に慣れていないと読みづらいと感じてしまいKotlinに抵抗感を感じる原因になってしまいます。

ラムダ式は怖くない!便利!読みやすい!チャレンジ!

解答例

fun containsEven(collection: Collection<Int>): Boolean = collection.any { it % 2 == 0 }

何も省略しない形

省略されている部分があるので、まず省略されていない形をみてみましょう。

fun containsEven(collection: Collection<Int>): Boolean = collection.any({ value: Int -> value % 2 == 0 })

anyの引数は、predicate: (T) -> Booleanのラムダになっています。Tを引数にとってBooleanを返すラムダをとるようになっています。今回のTはInt型が必要ですね。変数名がありません。変数名は呼び出しのタイミングでつけます。今回は勝手にvalueという名前にをつけました。呼び出す際にわかりやすい変数名につけてあげましょう。

ラムダ式の引数の型を省略

containsEvenの引数collectionは、CollectionはIterableであることが推論できます。つまりanyのforループで回るelementはInt型であることが推論できます。

//何か一つでもpredicateの条件と一致しているものがあったら、return true をするもの
public inline fun <T> Iterable<T>.any(predicate: (T) -> Boolean): Boolean {
    if (this is Collection && isEmpty()) return false
    for (element in this) if (predicate(element)) return true
    return false
}

ラムダ式の引数の型省略ができます。

fun containsEven(collection: Collection<Int>): Boolean = collection.any({ value -> value % 2 == 0 })

ラムダ式の引数の変数名を省略

ラムダ式の引数の個数が1つの場合は、itという変数名をデフォルトでつけてくれます。変数を省略できます。

fun containsEven(collection: Collection<Int>): Boolean = collection.any({ it % 2 == 0 })

()を省略

anyの最後の引数がpredicate: (T) -> Booleanとラムダを引数になっています。引数の最後がラムダの場合()を省略することができます。

fun containsEven(collection: Collection<Int>): Boolean = collection.any{ it % 2 == 0 }

余談

ラムダ式は、無名関数のシンタックスシュガー(のはず)なので、無名関数で書くと以下の通りです。

fun containsEven(collection: Collection<Int>): Boolean = collection.any(fun(value: Int): Boolean { return value % 2 == 0 })

あとがき

あまり詰め込みすぎるのも良くないので、今日はここまで。ラムダ式を覚えるとKotlinのサンプルが読めるようになります。ラムダは頻繁に使われるのでたくさん試しましょう。例えば一つ例に出します。

Androidで使われる例

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Log.v(TAG, "clicked");
    }
});

View.OnClickListener(v: View) -> Unitのラムダと同じとみれる(SAM変換)。

button.setOnClickListener { Log.v(TAG, "clicked") }

( Kotlinでリスナーやコールバックをスッキリと書く【関数リテラルとSAM変換】 - Qiita参考)

Day6.でまたお会いしましょう。