[毎日Kotlin] Day38. String and map builders

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

はじめに

毎日Kotlinシリーズです。

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

問題

String and map builders | Try Kotlin

Extension function literals are very useful for creating builders, e.g.:

fun buildString(build: StringBuilder.() -> Unit): String {
    val stringBuilder = StringBuilder()
    stringBuilder.build()
    return stringBuilder.toString()
}

val s = buildString {
    this.append("Numbers: ")
    for (i in 1..3) {
        // 'this' can be omitted
        append(i)
    }
}

s == "Numbers: 123"

Add and implement the function 'buildMap' with one parameter (of type extension function) creating a new HashMap, building it and returning it as a result. The usage of this function is shown below.

狙い

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

Builderでよく使うパターンですが、見慣れないとややこしく見えてしまうかもしれません。

thisがなんだったか思い出して解いてみよう。

[毎日Kotlin] Day10. Extension functions(拡張関数)

解答例

fun <K, V> buildMap(build: HashMap<K, V>.() -> Unit): Map<K, V> {
    val map = HashMap<K, V>()
    map.build()
    return map
}

buildMapの引数がbuild: HashMap<K, V>.() -> Unitになっています。buildMap内で使用する拡張関数buildと考えるとわかりやすいです。

使用例をみると、一時的な拡張関数とみれば、thisHashMap<K, V>であることがわかりますね。HashMap<K, V>のメンバ関数であるputが使用できます。

fun usage(): Map<Int, String> {
    return buildMap {
        put(0, "0") // this.putと同じ
        for (i in 1..10) {
            put(i, "$i") // this.putと同じ
        }
    }
}

別解: apply

Kotlinではちょっと便利なスコープ関数というあります。こちらを使う場面は沢山あります。詳細は割愛し、すでに良記事がありますので、ぜひこちらを参照。

今回の例でもつかえます。applyを使うのがよいと思います。

fun <K, V> buildMap(build: HashMap<K, V>.() -> Unit): Map<K, V> = HashMap<K, V>().apply(build)

apply at 1.1.0 · JetBrains/kotlin

@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }

別解: ラムダ式

通常のラムダ式でも同様のことできます。ラムダ式の場合は、引数として渡してあげればよいですね。

fun <K, V> buildMap(build: (HashMap<K, V>) -> Unit): Map<K, V> {
    val map = HashMap<K, V>()
    build(map)
    return map
}

ただし、この場合は、HashMap<K, V>は、レシーバーから引数になったので使用の仕方が異なります。

fun usage(): Map<Int, String> {
    return buildMap {
        it.put(0, "0") // thisではなくit
        for (i in 1..10) {
            it.put(i, "$i") // thisではなくit
        }
    }
}

しかし、(HashMap<K, V>) -> UnitHashMap<K, V>.() -> UnitはKotlin上では等価です。

つまり、以下のこともできます。

fun <K, V> buildMap(build: (HashMap<K, V>) -> Unit): Map<K, V> = HashMap<K, V>().apply(build)

あとがき

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