Kotlin1.3 まとめ

10/30日にKotlin1.3がりりーすされました。 1.2や1.3RCからの変更点等を主にJVMで動かすときに関わる範囲で適当にまとめようと思います。

変更・追加点

  • kotlinx.coroutines がexperimentalからstableに

  • capturing when subject

  • 引数なしエントリポイント main

  • contracts

  • Result<T> の実装

  • inline class の実験的なサポート

  • unsigned integer の実験的なサポート

  • Collection まわりでなんかいろいろ拡張関数追加された。

  • annotation calss をネストして宣言できるようになった。

  • @JvmStatic@JvmFieldcompanion object内で使えるようになった

  • Bool型が companion object を持つようになった。

  • 基本型のcompanion objectになんかいろいろ追加された。

適当にピックアップしてそれぞれ例を挙げて解説してみます。

引数なしエントリポイント

引数なしのmain関数がエントリポイントになるようになりました。
従来の main(args: Array<String>) は当然なくなってないです。
初心者フレンドリらしい。

fun main() {
    println("はろわ!")
}
//はろわ!

capturing when subject

when式に使う変数をwhen式の中で宣言できるようになったやつ。

when (val hoge = createRandomString()) {
    "hoge" -> println(hoge)
}

こんなかんじ
変数のスコープはwhen式の中に制限される。

Collection まわりの拡張関数

なんかいろいろ追加されてる。
SequenceorEmpty() とか Collection,Map,ListisNullOrEmpty()とかはAPIの一貫性を向上させるために用意されたらしい。

val strArray = arrayOf("aa", "bbb", "ccccccc")
strArray.isNullOrEmpty() //false
strArray.ifEmpty { arrayOf("hoge", "hoge") }

val strSequence: Sequence<String>? = strArray.asSequence()
strSequence.orEmpty()
requireNotNull(strSequence).ifEmpty { emptySequence() }

val strList: List<String>? = strArray.toList()
strList.isNullOrEmpty() // false
if (!strList.isNullOrEmpty()) {
    //contractsのおかげでスマートキャストされるようになった。
    strList.ifEmpty { listOf("hoge") }
}

Result

成功した処理の結果 T か 失敗した結果 Throwable を格納する共用体。
早速inline class で実装されてる。
1.3 m1 時点では SuccessOrFailureという名前で実装され rc-190 にて Result に名前が変わった。
自分は偶然 Resultってtypealiasを作って使っていたので I get kotonaki でした。
基本的にはrunCatching 関数を使って生成するものだと思う。

val result: Result<String>= runCatching {
    randomeFailuire() //一定確率で例外投げる関数だと思って
    "hoge"
}
result.onSuccess(::println)
    .onFailure(Throwable::printStackTrace)
fun Throwable.zero() = 0
val fold: Int = result.fold(String::length, Throwable::zero)

onSuccessT->Unitを受け取りResult<T>を返します。
成功した場合は当然それを実行。

onFailureThrowable->Unitを受け取りResult<T>を返します。

foldT->RThrowable->R を受け取り成功した場合は前者、失敗した場合は後者を実行しRを返す関数です。

多分よく使うのはこの3つだと思う。
他にもmap() とかgetOrDefault() とか便利なのいろいろ用意されてます。
ちなみにResult を返す関数を作るには今の所コンパイラ-Xallow-result-return-typeを渡す必要があります。

inline class

primary constructor で必ず一つの引数を受け取ってそれがコンパイル時にインライン展開されるかもしれない。
当然普通のクラスとは違っていろいろ制限がある。

  • primary constructorval でアノテートされたプロパティを一つだけ持つ必要がある。
  • backing field を持つ property(よく見る普通のプロパティ)は定義できない。
  • initializer block を持てない。
  • === (参照等価性)での比較は不可

他にも制限あったかもしれない。
正確には必ずコンパイル時にプロパティがインライン展開されるわけではなくて、可能な限りインスタンスの生成等を行わずに済むように最適化しようとする感じです。
その最適化を行うために上記の制限があるみたい。

fun main() {
    val age = Age(14)
    val ageValue = age.value
    println(age)
    println(ageValue)
    println(age.birthYear())
}

inline class Age(val value: Int) {
    fun birthYear(): LocalDateTime {
        //todo 雑
        val now = LocalDateTime.now()
        return LocalDateTime.of(
            now.year - value, now.month,
            now.dayOfMonth, now.hour, now.minute
        )
    }
}

こんな感じのコードはコンパイルされると

fun main() {
    val age: Int = Age.constructor_impl(14)
    val var2: Age = Age.box_impl(age)
    println(var2)
    println(age)
    val var3:LocalDateTime = Age.birthYear_impl(age)
    println(var3)
}

こんな感じになります。
クラスの実装は割愛です。

inline class を使用するにはコンパイラに引数として-XXLanguage:+InlineClasses を渡す必要があります。

kotlinx.coroutines

これは例とか出してるとやたら長くなりそうなので 1.0.0 での変更点を自分が実際に使った際に気づいたところを軽くまとめようと思います。

  • グローバルでsuspend propertycoroutineContext が消えてCoroutineScopeのプロパティになった。

  • launch() 等の関数が GlobalScopeオブジェクトの拡張関数になった。

  • launch()async から parent: Job? の引数が消えた。

  • async(coroutineContext + CommonPool){}とかしてたと思うけどCommonPool消えので ForkJoinPool.commonPool().asCoroutineDispatcher()とかする。

  • launch(UI){ } とかしてたと思うけどUI 消えた。Dispatchers.Mainに置き換える

launchasync くらいしか使わないから気づいたこと少なすぎた…
んぐぉぉぉぉぉ……

contracts

コンパイラになんかいろいろ教えてあげるやつ。
これのおかげで特定のメソッド呼び出しの結果が true ならレシーバがnon-null型にスマートキャストされたりとか、ラムダ式が一度だけ同期的に呼び出されることを保証してその中で外のスコープの変数を初期化できるようになったりとかする。
早速Array<T>?.isNullOrEmpty()とか requireNotNull() とか T.run() とかに使われている。

使い方の例として一度だけ呼ばれるラムダ式contracts を挙げ…ようとおもったけどなんかビルド通らないな…んんん~~また今度。
1.3-m2 が出て即試したときは普通にできたけどBackend Internal error吐きまくってなんかビルドできないので寝ます。

標準ライブラリのrequire関数とか使う分には問題なく動きます。
以上

なんか変なこと書いてたりしてたら指摘していただければ嬉しいです。

参考

Kotlin 1.3リリース – コルーチン、Kotlin/Nativeベータ | JetBrains ブログ

What's New in Kotlin 1.3 - Kotlin Programming Language