多値と JVM

JVM は多値が素直に扱えないよ、という話。

多値とは、式が複数 (>=2) の値を結果とすることです。 Go 言語では積極的に使われているみたいです。求める結果と、エラー値を、併せて戻す、みたいに。

file, error := os.Open(name)
if error != nil {
    return error
}
...

多値をサポートする言語は、 Go の他には SchemeCommon Lisp があります。 RubyPython なんかでも同じように書けますが、こいつらは代入の右辺値が配列/タプルで、そいつをバラして左辺に渡すので、いわゆる多値ではないと認識しています。多重代入。 C も多値をサポートしておらず、多重代入もできないので、関数から複数の値を受け取るには、変数のアドレスを渡すのが一般的です。 Java は多値をサポートしておらず、多重代入もできず、変数のアドレスも渡せないので、メソッドから複数の値を受け取るには、オブジェクトに固めて戻してもらいます。

C や Java など、 C 系の言語では、式にふたつのバリエーションがあると言えます。

  • void 型の式: 式の値が 0 個
  • void 型以外の式: 式の値が 1 個

Go などでは「式の値が 2 個以上」ってのがあって、それが多値ですが、 C や Java には存在しない。

で、 JVM は基本的に Java 言語を動かすために作られたので、多値が素直に扱えません。

JVM はスタックマシンなので、メソッドを呼び出す際には引数をスタックに積んで呼び出し、戻ってくると戻り値がスタックに積まれています。で、呼び出し先が void 型であれば、 0 個の値がスタックに積まれて戻り(つまり何も積まれない)、呼び出し先が void 型以外であれば 1 個の値がスタックに積まれて戻ります。 2 個以上の値がスタックに積まれて戻ることはない。ここがちょっと気持ち悪いわけです。引数は 0 個でも 1 個でも 2 個でも 3 個でも積めるのに、戻り値は 0 個か 1 個しか積めない。

invokedynamic は Java 以外の言語を JVM 上で動かすために追加された命令ですが、やはり戻り値の型は void 型(式の値が 0 個)かそれ以外(式の値が 1 個)の二択です。したがって、 RubyPython を実装するには充分ですが、 Go や Scheme を実装するには不充分です。 JVM を多言語向けに改造するプロジェクトである Da Vinci Machine Project でも、多値サポートの計画はないようです。

とはいえ、「多値を表すクラス」とかを導入して、そいつを戻すようにすればいいので何とでもなるわけですが、なんかもやもやして気持ち悪いのでこの記事を書きました。

参考: