算譜王におれはなる!!!!

偏りはあると思うけど情報技術全般についてマイペースに書くよ。

Kotlin M5.3発表:Delegated PropertyとCallable Reference(あと勉強会開催します #jkug)

Kotinのマイルストーン5.3が発表されました。

Kotlin M5.3: IntelliJ IDEA 13, Delegated Properties and more | Project Kotlin

IntelliJ IDEA 13への対応と、間もなくAndroid Studioにも対応すること、コンパイラの性能改善とIDEの機能増強について言及されています。その他にKotlin言語の新しい機能とそのシンタックスも発表されています。特に面白いと思った2つの機能について紹介します。

Delegated Property

プロパティを遅延初期化したり、値の変更の監視をしたい場面がありますよね?そんなときはコードをごりごり書いてきましたが、Delegated Propertyはこのような仕掛けを用意するための機能を言語レベルで提供します。 次のサンプルコードを見てください*1

class Example {
  var p: String by Delegate()
}

class Delegate() {
  fun get(thisRef: Any?, prop: PropertyMetadata): String {
    return "${thisRef}の${prop.name}が参照されました。"
  }

  fun set(thisRef: Any?, prop: PropertyMetadata, value: String) {
    println("${thisRef}の${prop.name}に${value}が割り当てられました。")
  }
}

プロパティの型の後にbyを置き、続けて委譲先のオブジェクトを指定します。このプロパティへのアクセスに対して委譲先のオブジェクトが働いてくれるというわけです。プロパティに対するgetとsetは、それぞれ委譲先オブジェクトのgetメソッド、setメソッドが呼び出されます。委譲先オブジェクト(ここではDelegateオブジェクト)は何か特別なトレイトを実装してはいない点に注意してください*2。メソッドの有無で委譲先オブジェクトとして妥当かどうかをコンパイラは判断します。

val e = Example()
println(e.p)
e.p = "NEW"

このコードの実行結果は次のようになります。

Example@6c22c95bのpが参照されました。
Example@6c22c95bのpにNEWが割り当てられました。

この機能を使えば、プロパティの遅延初期化や変更監視が簡単にできるようになります。が、それらを自分で実装する必要はありません。Kotlinの標準ライブラリkotlin.propertiesパッケージ内に(特にDelegatesシングルトンオブジェクト内に)委譲先オブジェクトのファクトリ関数が揃っています。例えば遅延初期化のためのkotlin.properties.Delegates.lazy関数は次のように使用します。

import kotlin.properties.Delegates

class Example {
  val p: String by Delegates.lazy {
    println("初期化")
    "hoge"
  }
}

fun main(args: Array<String>) {
  val e = Example()
  println("-----")
  println(e.p)
  println(e.p)
}

このコードの実行結果は次のようになり、初期化の遅延が上手くいっていることがわかります。

-----
初期化
hoge
hoge

Callable Reference

Kotlinには高階関数の仕組みがあり、つまり関数をファーストクラスとして扱うことができます。ただし!ファーストクラスなのは関数リテラル表記で生成した関数のみで、funキーワードを伴って定義された関数はファーストクラスではありませんでした。次のコードは期待通りに動きます。

fun main(args: Array<String>) {
  val next ={(n: Int) -> n + 1}
  println(listOf(1, 2, 3).map(next)) // [2, 3, 4]
}

次のようにfun宣言された関数では上手くいきません。

fun next(n: Int) = n + 1

fun main(args: Array<String>) {
  println(listOf(1, 2, 3).map(next)) // ここでコンパイルエラー
}

この問題を新機能Callable Referenceは解決してくれます。fun宣言された関数の参照を渡すには、::(コロン2個)を関数名の前に付けるだけです。次のコードはfun宣言された関数の参照を関数の引数として渡して期待通りに計算を行います。

fun next(n: Int) = n + 1

fun main(args: Array<String>) {
  println(listOf(1, 2, 3).map(::next)) // [2, 3, 4]
}

これは非常に便利です。なぜ今までなかったのか不思議なくらいです。この機能はまだまだ発展途上ですのでご注意を(nextmainのローカル関数にすると実行時エラーが起こりました)。

第1回かわいいKotlin勉強会

ついに!Kotlinの勉強会を開催することとなりました! ぜひ奮ってご参加ください!! スピーカー募集中です。

第1回 かわいいKotlin勉強会 #jkug

*1:Kotlin公式ブログから引用。一部改変。

*2:標準ライブラリを見ると、publicな委譲先オブジェクト用トレイトが提供されているので、これを使った方がいいと思います。