kmizuの日記

プログラミングや形式言語に関係のあることを書いたり書かなかったり。

Kotlinのブロックからなる関数定義でreturnを書かなくて良いようにする

Kotlinでは、一つの式からなる関数は

fun add(x: Int, y: Int): Int = x + y

のように明示的なreturnを必要としません。次のように複数の式からなる関数定義ではreturnが必須となります。

fun printAndAdd(x: Int, y: Int): Int {
  val k = x + y
  println(k)
  return k //必須
}

この制限の理由について、Kotlinのリファレンスでは

Kotlin does not infer return types for functions with block bodies because such functions may have complex control flow in the body, and the return type will be non-obvious to the reader (and sometimes even for the compiler).

と書いているのですが(強調は筆者による)、変な話です。何故かというと、ラムダ式複数の式を持てるのに、ちゃんと型を推論できているからです。

一方、Kotlinにはinlineという修飾子を関数につけることができて、これは、inline指定された関数を呼び出すときに、インライン展開させるように指示するものです。たとえば、

inline add(x: Int, y: Int): Int = x + y
println(add(1, 2))

とした場合、これは

println(1 + 2)

と同じコードとなることが期待できます。これを利用して、ブロックからなる関数定義でも、明示的にreturnを書かなくてよいようにする小技を思いついたのでご紹介します。方法は簡単で、

inline fun <T> block(body: () -> T): T {
    return body()
}

という関数を定義して、

fun printAndAdd(x: Int, y: Int): Int = block {
  val k = x + y
  println(k)
  k //return不要
}

のように、=に続けて、定義したblock関数を呼び出すだけです。実に簡単です。さて、こうした場合の実行性能ですが、一つ定義するごとに、getstatic命令とその直後にpop命令が余分に追加されていました。両方とも命令の実行コストは非常に軽いことが予測されますし、無意味な命令としてJVMJITコンパイラが除去してくれる可能性もあります。いずれにせよ、通常気にする必要のあるオーバーヘッドは発生しません。

現在、そういう制御フローに関する小物関数を集めたライブラリを作ろうかななどと考えています(考えるだけでやるとは言っていない)。