Roppongi.rb#10 で「Self-hosting Whitespace」というタイトルでLTをした
タイトル通り、Roppongi.rb#10 でSelf-hosting WhitespaceというタイトルでLTをしました。ディープな話題ですが楽しんでもらえたようでよかったです。
この記事では、発表の際に話せなかったことや、発表を見ていない方への補足を書きます。 なお、スライドを見る場合にはGoogle Slideのものをおすすめします。結構真面目に話す内容をメモ書きしています。
発表の要旨
まずは、前提知識としてセルフホスティングとWhitespaceについて解説をしました。
そして、その後にWhitespaceで書かれたWhitespaceのインタプリタの実装について解説をしました。 interp.ws · GitHub
このインタプリタは、Whitespaceを手書きしたのではなく、RubyをWhitespaceにコンパイルすることによって作られています。
そのため、実はこの発表のメインは「RubyをWhitespaceにコンパイルするコンパイラ」についてです。 このコンパイラ(言語)をWsrbと名付けました。
発表の補完
発表の際に話しきれなかったことを書こうと思います。
リンク集
まずは、今回の発表に使用したソフトウェアなどのリンクをまとめておきます。
- interp.ws · GitHub
- 先ほどものせましたが、Whitespaceで書かれたWhitespaceのインタプリタです。
- GitHub - pocke/akaza: A cool Whitespace language implementation in Ruby.
- GitHub - pocke/self-hosting-whitespace: DEMO repository of Roppongi.rb#10
- GitHub - pocke/gows
- WhitespaceをRubyに埋め込む - pockestrap
- 発表中でもちらっと話した、Rubyのコード中にWhitespaceを埋め込む機能についての記事です。
- RubyをWhitespaceにトランスパイルする - pockestrap
- Wsrbの初期実装の段階での記事です。この頃のコードにガッツリ手を入れることで今のWsrbができました。
- 最初はセルフホスティングまでやろうという気持ちはなかったのですが、どうしてこうなってしまったのか…
「Whitespaceは読めない以外は意外と簡単な言語」
発表中に、「Whitespaceは読めない以外は意外と簡単な言語」という発言をしました。
コードを読めないこと以外はWhitespaceは簡単 #roppongirb
— シロ (@shiroemons) May 24, 2019
これはまあウケ狙いな発言でもありますが、わりと事実でもあると思っています。 Whitespaceは言語仕様は単純で、その割にサブルーチン呼び出しなどの高級な機能があったりスタックマシンだったりして、プログラムをコンパイルする先としては結構扱いやすいものだと思います。 他に言語を作ったわけではないので本当にそうかはわからないのですが……。
ただ、この発言でも言っている通り人間には見えないのは面倒です。そのためWsrbの実装ではRubyのASTを直接Whitespaceのコードに変換するのではなく、一旦中間言語に変換しています。
また、まともなリファレンスが全然存在しないのはWhitespaceを実装する上でハードルとなります。Wikipediaの記事には一部のコマンドしか書かれていませんし、Matzの記事も参考にはなりますが実装するには詳細な情報が足りません。 そして本家と思われるサイトは死んでおり、Web Archiveに頼る必要があります。 https://web.archive.org/web/20150618184706/http://compsoc.dur.ac.uk/whitespace/tutorial.php
セルフホスティングしたWhitespaceインタプリタの微妙な非互換
実は今回のWhitespaceで書かれたWhitespaceインタプリタは、少しだけ非互換があります。
Whitespaceでは、入力を標準入力1つしか扱うことができません。つまり、ファイルからプログラムを読み込む、といったことができません。
これはセルフホストされたインタプリタの上でプログラムを動かす上で問題になります。例えば、以下のようにインタプリタを実行することができません。
# Rubyで実装されたinterp.rbというRubyインタプリタがある場合、この様にコマンドライン引数にファイルを重ねて書けばよいだろうということが期待できる。 $ ruby interp.rb main.rb # Whitespaceにはコマンドライン引数を扱う機能など存在しないので、これは不可能 $ gows interp.ws main.ws
Whitespaceに唯一ある入力は標準入力です。そのため、今回は標準入力から実行するプログラムを受け付けるようにしました。
そして、インタプリタ上で実行されるプログラムと、そのプログラムが扱う標準入力を区別するため、プログラムの終わりに$
を入れる必要があります。
$ echo -n '$' > separater # 標準入力からmain.wsを読み込んで、interp.wsの上で実行する $ cat main.ws separater | gows interp.ws # 標準入力からmain.wsとmain.wsへの入力input.txtを読み込んで、interp.wsの上で実行する $ cat main.ws separater input.txt | gows interp.ws
これってセルフホスティングなのか
自分でもよく分かっていないのですが、今回のWhitespaceインタプリタの実装がセルフホストなのかに疑問があります。 このインタプリタはRubyのコードをコンパイルすることで得られます。つまり、Whitespaceを手書きしたわけではないし、生成されたWhitespaceのコードを人間が手で修正するのは難しいと言えるでしょう。
Whitespaceインタプリタの上でWhitespaceを動かすことができるとは言え、このようなケースはセルフホスティングと言えるのか疑問があります。 今回の発表では分かりやすさのためにセルフホスティングと言ってしまいましたが、間違っていましたらすみません。
Hashの実装
スライドにもあるとおり、Hashの実装はハッシュテーブルと連結リストです。
yuya_takeyamaさんによるRubyでのHashの実装があり、そちらがわかりやすいと思います。 ほとんど同じ実装をしています。
Hashの実装はわりと鬼門で、バグを生みまくっていました。(Hashの実装の他は、配列の再確保とメソッド呼び出しが大変だった…)
既知のバグ
Wsrbには既知のバグがあります。メソッド呼び出し時のローカル変数とselfの取扱がハチャメチャで、バグっています。直そうと思ったのですが思ったよりめんどくさく、かつ今回書いているプログラムの範囲内であればいい感じに動いてしまっているのでめんどくさくて直していません。
少し具体的に言うと、ローカル変数やselfの置き換えを引数の評価の前にやってしまっていることが原因です。 Wsrbではメソッド呼び出し時、現在のスコープのローカル変数を全てスタックに退避させて、メソッド呼び出しの後に退避させたローカル変数を巻き戻しています。 またselfはなんかごちゃごちゃやっています(忘れた…)。
これらの処理を引数の評価の前にやってしまっているため、メソッド呼び出しの引数でローカル変数の中身を書き換える処理があったり、メソッドを更に呼び出すようなコードがあると多分壊れます。
Thanks
福岡でのRubyKaigiの発表を聞いてRubyVM::AST熱に浮かされなかったら、Whitespaceをセルフホスティングすることも発表をすることもなかったでしょう。 素晴らしい発表を見せていただいたjoker1007さんとtagomorisさんにはとても感謝しています 🙏 そして、その他のRubyKaigiでの発表も自分にとってとても刺激になりました。発表せずにカンファレンスに参加すると、こんなにも人前で話したくなるんですね。
また、hanachin_さんには事前にコードを見てもらっていました(お返しにRuby Puzzleを見せてもらいました)。面白いものを作って今すぐ話したい気持ちだったのに中々人に話せずにいたので、コードを見て感想をもらえたのは嬉しかったです。ありがとうございました。
まとめ
役に立たないプログラムを書くほど楽しいことはないのでオススメです!書いていきましょう!!
そして、実は役に立たないプログラムを書くことは決して無駄ではないと思っています。 もちろんそのプログラム自体は役に立ちませんが、普段は意識していないことや普段は触れない技術について実装する過程で知ることができます。これは知的好奇心を満たすだけでなく、役に立つプログラムを書く場合に役に立つこともある……んじゃないかなと思っています。