kmizuの日記

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

Kotlinでメソッド定義にrunを使う意義

Kotlinにはrun というメソッドがstdlibにあります。これ、定義をみると、

@kotlin.internal.InlineOnly
public inline fun <R> run(block: () -> R): R = block()

引数で渡された0引数ラムダ式(とKotlinの用語法に従っておく)をそのまま呼び出すだけというものになっています(もう一バージョンあるのですが、そっちについては割愛)。本来の意味での用途かはわからないですが、これが役に立つケースとして、メソッド定義でreturnを書かなくても済むというものがあります。

たとえば、引数を取り、それぞれを出力した後、二つの値を足したものを返すメソッドprintAndAddは、通常は以下のように書く必要があります。

fun printAndAdd(x: Int, y: Int): Int {
  println(x)
  println(y)
  return x + y
}

さて、このreturn必須というのが、(式ベースの言語から来ると)地味に嫌なわけですが、この制限をrunを使って回避することができます。以下のように書き換えるだけです。

fun printAndAdd(x: Int, y: Int): Int = run {
  println(x)
  println(y)
  x + y
}

runにその場で生成したラムダ式を渡して実行するという形になりますが、ラムダ式の返り値の型をKotlin処理系が推論してくれるため、書く必要がなくなっています。

ところで、メソッド呼び出しのたびにラムダ式が生成されるというと実行コストが気になる人がいるかもしれませんが、心配ご無用。runにインラインで渡された無名関数はそのままインライン展開されるため、オーバーヘッドはほぼゼロです。

試しに、次のコードをコンパイルしてみます:

fun add(x: Int, y: Int): Int = run {
  x + y
}

javapした結果:

Compiled from "P.kt"
public final class PKt {
  public static final int add(int, int);
    Code:
       0: nop
       1: iload_0
       2: iload_1
       3: iadd
       4: ireturn
}

nopが入ってしまっているのが多少残念ですが、さすがにこのレベルで無駄なコードはJITコンパイラが消去してくれることを期待できますし、万が一消去してくれなかったとしても、メソッド一つにつきnop命令一つ分のオーバーヘッドはほぼ無視できます。

ただ、性能面での問題点はないものの、Kotlinの標準的な慣習に沿っていないという点はやや微妙かもしれません。まあ、その辺は統一してrunを使っていれば問題は少ないのではないかとも思いますが。