メッセージ送信とrhinoでjavafx

JavaFXのスタートアップ諸々には作法がある(静的な型情報を用いたリフレクションを多用)ので動的なJVM言語でスクリプトを書くと起動でコケるはず。

これは静的な型と言うことは実行時にJavaFXの型にコンパイルされていれば問題ないが、本来、動的言語がいつコンパイルするかは不定なので動的言語とは本質的に相性が悪い。

rhinoの場合はJavaAdapterでは不適切でjscでjavafx.application.Applicationを継承してAOTコンパイルすれば良い。

ではなぜJavaAdapterでは不適切かというとJavaAdapterはJavaObjectを返す内部[[Construct]]メソッドを持ったオブジェクト、要するにコンストラクタ関数だからだ。

JavaObjectとはJSObjectと並
ぶnetscape伝統のjavaからjavascriptを操作するオブジェクト群で、JSObjectはjavaからjsを──JavaObjectはjsからjavaを扱うためのプロトコル。
で、JSObjectは割愛するとしてJavaObjectがどうやってJava(のオブジェクト)を扱っているかというと単にメッセージ送信を行なっているだけ。ただし、jsのオブジェクトはjavaのオブジェクトが持っているメソッド群を本来は持ち合わせていないため、固有の内部[[Get]]、[[Put]]/[[Set]]の振る舞いを持ってメッセージ転送を行なっている。javascriptでメッセージ転送というと__noSuchMethod__だが、これはesどころかjsの仕様にすらないspidermonkeyの独自拡張。だから__noSuchMethod__を定義しているわけではなく直接、内部[[Get]]、[[Put]]を変更している。

ただし、メッセージ送信とメッセージ転送は別物なので注意。jsは元からメッセージ送信を持っているが仕様ではメッセージ転送を持っていないので存在しないメッセージを扱うためにメッセージ転送を行うよう内部[[Get]]、[[Put]]を変更する必要がある。

この様に内部の振る舞いを変更するというのはMOPの手法なのでJavaObjectは二種類の方法を用いてjavaを扱うという問題を解決していることになる。メッセージ送信が正しく行われるならメッセージに対応する関数が存在するということなので、即ち正しくメソッドサーチされるということ。


メッセージ送信は実行時に行なわれるものなのでそれがjavaのオブジェクトでなくても(つまり、javaの型じゃなくても)実行時にjavaのメソッドを発見出来れば良い──メッセージ転送によって存在しないメッセージを正しく送信出来ているのでこれは成功する。javaのメソッドを発見出来なければそれは単にメソッドサーチに失敗するだけ。その場合、LiveConnectがエラーを通知する。


さっきから静的な型だとか動的言語だとか言っているが実はこれは正しくない。
なぜなら動的言語でも静的な型を持っているからだ。動的言語の「動的」とは型が実行時に柔軟に変わるという意味で「弱い型付け」とか「強い型付け」の強弱とはこの挙動を指している。だからV8が暗黙の静的な型を持っているという趣旨の言い方は間違っている。
モノタイプでなければ動的言語が静的な型を持っているのは当たり前だからだ。おそらく"静的な"型という言い方がややこしい。動的に変換されるの方がふさわしい。V8は動的に変換する必要のない型をそのままで扱っているだけだ。これがjavascriptにおけるアーリーバインディングの正体だ。アーリーだのレイトだの関係なしに動的言語なのだから"実行時に"変換する必要のない型をただ変換せずにそのまま扱っているに過ぎない。実行時の情報を用いるのが動的言語の特徴なのでこれは自然なことでjavascript2.0やAdobeのes4RIでもvar foo : Number;とかvar foo as Number;とすることで出来た。

今のjsにこれがないのは型が静的だとか動的だとか関係なしに常にリロードされる可能性のあるブラウザにおいてアーリーバインディングとレイトバインディングの混載が非常に面倒だからTC-39では入れないと決定しただけ。js仕様の問題ではなくブラウザの都合を優先した結果だ。


ブラウザの場合リロードされる度にキャッシュされたマシン語のチェックと再コンパイルが発生するしリロードされる度に状態が変わる変数はキャッシュが必ず無効になるのでいちいち型推論してまでアーリーバインディングした上で早々とマシン語にしてキャッシュするメリットが薄い。型推論とキャッシュとコンパイル作業は充分遅いからブラウザでの実行速度にこだわるTC-39には仕様にアーリーバインディングがない方が早い実装が作れるからそっちの方が都合が良いということ……結局どの問題もここに行き着く。


最初に言った実行時にJavaFXの型にコンパイルされていれば問題ないというのは表現形式の変換だけではなく型変換も行なわれているからだ。つまり最終的にJavaFXを使う直前までにJavaFXの要求する型になっていれば良いので型変換のタイミングは重要ではない。ただ、javaのinvokedynamicのような仕組みがなければコンパイルした時点で型が固定されてしまうためこれは動的言語らしくない。




rhinoのJavaAdapterは最終的に表現形式は変換されるが型はJavaObjectなのでjavafx.application.Application#lunch(Class)に失敗するというカラクリ。

rhinoはかなり素直に動的言語として実装されている。