V言語のdiscordやissueを見ていると,「replを改善してほしい!」という意見をちょこちょこ見かける. たしかにV言語のreplは比較的未成熟で,改善できそうな点がある.いくつか例を挙げてみよう.
- 評価結果は
println
を使わないと確認できない - 別の行で定義した変数や関数は利用できない
これでは確かに不便で,要望に上がるのも頷ける. では,この問題点は何が原因で,どうすれば解決できるんだろう.
これらの問題は,replの実装方法によるもので, 早い話が 各行を別々にコンパイルし,実行する という実装になっているのである.
そのため,評価結果は println
でしか確認できず,別の行で定義した変数や関数は利用できない.
僕がたどり着いた解決方法は次の2点だ
- ソースコードをバッファする
- インタプリタ・JIT形式の実行エンジンを実装する
それぞれについて詳細を解説する.
この方式では,replを起動してから入力された全ての行を,終了時まで記憶しておく. つまり,バッファしているソースコードの末尾に,入力されたソースコードを追記したものをコンパイル・実行するのである.
しかし,この方式には,評価に時間のかかる処理を何度も実行してしまう,という難点がある. 例えば,以下のような疑似コードを入力したと思ってほしい.
>> a := 2
>> sleep(3)
>> b := 1 + 2
この場合,2行目で入力した sleep(3)
が3行目でも実行されてしまうので,1 + 2
を評価するのに少なくとも3秒もかかってしまう.
これはあまり望ましくないかもしれない.
この方式では,入力されたソースコードをコンパイラに渡さず,その場で評価し,実行する. 評価・実行するのはrepl自身なので,変数や関数の評価結果は記憶しておけるし,よって,他の行で定義された変数や関数を利用するために評価し直す必要もない. (インタプリタ・JITのどちらを採用しても問題ないのだが,ここではJITを採用することを仮定する)
例えば,以下のような入力があったとする.
>> fn sleep_and_add1(x int) int {
.. sleep(3)
.. return x + 1
.. }
>> a := sleep_and_add1(3)
各行で,replが何をするか見てみる.
まず,最初の3行では関数の定義が行われている. ここでreplは,以下のように動く.
- 関数の型を読む
- 関数の本体をパースし,vm命令に変換する
- シンボルテーブルに
sleep_and_add1
を登録する
注目すべき点は,シンボルテーブルが導入されていることだ.
これにより,他の行で sleep_and_add1
を使いたい場合はシンボルテーブルを参照するのみで済む.
関数全体をパースし直す必要はない.
次の行では sleep_and_add1
を呼び出し,その結果を a
に代入している.
replは以下のように動くだろう.
- 代入演算子の左辺を読む
- 同様に右辺を読み,シンボルテーブルの
sleep_and_add1
を参照する - 関数呼び出しをvm命令に変換し,実行する
- 呼び出し結果とともに
a
をシンボルテーブルに保存する
この方式では実行の度に全体を評価し直す必要がなく,問題を解決できていることがわかると思う.
V言語にパッチを投げることを考えると,ソースコードをバッファする方式に落ち着きそうな気がする. というのは,JIT・インタプリタ方式だと,比較的実装コストが高いのだ. V言語はまだ未成熟な段階なので出来なくはないだろうが,コンパイラにバックエンドを追加する作業に等しい. できるかどうか問われると,あんまり自信がない.
とりあえずdiscordに投げてみて意見を聞いてみたいけど,どうしたものかなぁ...