kmizuの日記

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

宣言と初期化と代入と

Onionの仕様についてつらつらと考えていたら、自動ダウンキャスト以上になんかよろしくなさそうな箇所に気付いてしまった。というか、もっと早くに気付いとけよという代物だけど。

Onionは静的型付け言語で、ローカル変数も(他の静的型付け言語同様に)静的に型付けされるのだが、代入式の左辺に現れるローカル変数が、その時点において宣言されていなければ、右辺の型から左辺のローカル変数の型を決定(あえて推論とは言わない。なんというか、推論してるというイメージではないので)する方式を取っている。たとえば、

x = 100;

というコードがあって、この時点でxが宣言されていなかった場合、100というリテラルをint型とみなして、

x: int = 100;

という風に型宣言を自動的に補完してくれる。

ML系とかHaskellとかの元々型推論が強力な言語はとりあえずおいといて、最近の静的型付けOOP言語でもこういう機能を持った言語は別段珍しいものではなくて、たとえばC# 3.0,Scala,Nemerle,Boo,Nice,Satherなどなど枚挙に暇が無い(と言う程一般的かどうかはちょっと自信が無い)。

この機能、一見したところ特に問題がなさげに見えるし、実際上で挙げた言語では特に問題は起こらないと思う。しかし、Onionの場合、代入中で変数宣言ができてしまうためにちょっと嫌らしい問題が起きてしまう。

たとえば、以下のOnionのコードを考えてみる:

if((x = true) || (y = true)) {
  System::out.println(y);
}

現在のOnionの仕様では、このif文の前に変数xとyが宣言されていなかった場合、xとyがif文の中でのみ有効なboolean型のローカル変数として宣言されることになる。しかし、ちょっと考えてみればわかる通り、代入式

x = true

の評価値はtrueになってしまうため(代入式は代入された値を返す)、||の右側の式が評価されず、未初期化の変数yをprintlnするコードになってしまう。で、この文を現在のOnionの処理系に食わせるとどうなるかというと…

Exception in thread "main" java.lang.VerifyError: (class: ifMain, method: start signature: ([Ljava/lang/String;)V) Accessing value from uninitialized register 3
	at java.lang.Class.forName0(Native Method)
	at java.lang.Class.forName(Class.java:247)
        ...(以下略)...

のように、VerifyErrorを吐かれてしまう(バイトコードベリファイア様様というか、こういうヤバゲなコードがすぐに分かるのは、自分みたいに処理系作るときに凡ミスしがちな人間にとっては、実にありがたい)。自分がぱっと思いつく対処法としては、大きく分けて三つある:

  1. フロー解析とかをやって未初期化の可能性がある変数の参照を保守的にコンパイルエラーにする
  2. とりあえず、必ず型ごとのデフォルト値で初期化するようにしておく(intなら0,booleanならfalse,参照型ならnullといった具合)
  3. 式中で変数宣言できてしまう現在の仕様を考え直す(で、C# 3.0やScalaみたいにvarキーワードとかで宣言する変数でのみ型の省略が有効なようにする)

1.は解析を行う手間と得られるメリットを考え合わせるとあまり割にあってない気がするし、2.は無駄にパフォーマンス上のペナルティを背負うのであまり嬉しく無い。というわけで、式中で変数宣言できてしまう現在の仕様は削除して、varみたいなキーワードを導入して、明示的な宣言が必要なように変更しようかと考えている。