[毎日Kotlin] Day39. The function apply

2018.03.23

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

はじめに

毎日Kotlinシリーズです。

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

問題

The function apply | Try Kotlin

The previous examples can be rewritten using the library function apply (see examples below). Write your own implementation of this function named 'myApply'

fun <T> T.myApply(f: T.() -> Unit): T { TODO() }

fun createString(): String {
    return StringBuilder().myApply {
        append("Numbers: ")
        for (i in 1..10) {
            append(i)
        }
    }.toString()
}

fun createMap(): Map<Int, String> {
    return hashMapOf<Int, String>().myApply {
        put(0, "0")
        for (i in 1..10) {
            put(i, "$i")
        }
    }
}

狙い

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

スコープ関数を自作して仕組みを理解しよう。プチBuilderとして大活躍すること間違い無し。

解答例

fun <T> T.myApply(f: T.() -> Unit): T {
    f()
    return this
}

これだけなんです。こういうちょっとしたコードが意外と便利だったりするんです。これでも十分なんですが、これだと関数呼び出し分遅くなってしまいます。

// 拡張関数を使わない、ベタな実装
fun createString(): String {
    val builder = StringBuilder()
    builder.append("Numbers: ")
    for (i in 1..10) {
        builder.append(i)
    }
    return builder.toString()
}

// 拡張関数つかった実装
// 関数呼び出し分遅い
fun createString(): String {
    return StringBuilder().myApply {
        append("Numbers: ")
        for (i in 1..10) {
            append(i)
        }
    }.toString()
}


文法が簡単になるかわりに速度を犠牲に。。。っと諦めようとしてるあなた!Kotlinなら**inline**という強い見方があります。単純にinlineを付けてあげます。


inline fun <T> T.myApply(f: T.() -> Unit): T {
    f()
    return this
}

こうすると、ラムダ式の部分がコンパイル時に展開されて、コンパイル後は、以下と同じようなコードになっています。

fun createString(): String {
    val builder = StringBuilder()
    builder.append("Numbers: ")
    for (i in 1..10) {
        builder.append(i)
    }
    return builder.toString()
}

おまけでJavaに戻した時のコード。

   @NotNull
   public static final String createString() {
      Object $receiver$iv = new StringBuilder();
      StringBuilder $receiver = $receiver$iv;
      $receiver$iv.append("Numbers: ");
      int i = 1;

      for(byte var3 = 10; i <= var3; ++i) {
         $receiver.append(i);
      }

      String var10000 = $receiver$iv.toString();
      Intrinsics.checkExpressionValueIsNotNull(var10000, "StringBuilder().myApply …       }\n    }.toString()");
      return var10000;
   }

あとがき

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