コンテンツへスキップ
GitHubリポジトリ フォーラム RSS-ニュースフィード

Ruby Thursdayよ、さらば

Ary Borenzweig

ご存知ないかもしれませんが、CrystalはRubyに似た構文とセマンティクスを持つプログラミング言語です。ただし、インタプリタではなく、ネイティブコードにコンパイルします。

そこで、RubyでCrystalのコンパイラを実装することにしました。なぜでしょうか?

  • Rubyは素晴らしい言語であり、エレガントな構文を持ち、非常に高速なプロトタイピングに適しています。
  • いつかCrystalでコンパイラを書き、構文とセマンティクスがRubyと似ているため、コードの移植は比較的容易になるでしょう。

Crystalでコンパイラを実装しようと何度も試みましたが、常に問題がありました。

コンパイル時間が長すぎる

これは当初、Rubyが時々遅いからだと考えました。

しかし、当初の言語は純粋で、Rubyと非常に似ており、型を指定する必要が全くありませんでした。コンパイル時間はコードサイズと配列のインスタンス化の数に比例して指数関数的に増加しました。コンパイラのほんの一部をコンパイルするだけでも数分かかるようになりました。これは受け入れられないと判断しました。

そこで、小さな犠牲を払いました。配列、ハッシュ、その他の汎用型の型を指定する必要がある場合があります。

a = []          # OK for Ruby, but not for Crystal
b = [] of Int32 # OK for Crystal

c = [1, 2, 3]   # OK for Ruby and for Crystal
c << 4          # OK for Ruby and for Crystal
c << "hello"    # OK for Ruby, error for Crystal (c is Array(Int32))

d = [1, 2, 3] of Int32 | String
d << "hello"    # OK for Crystal

この小さな変更により、コンパイル時間は大幅に向上し、コードサイズに線形に比例して増加するようになりました。

しかし、それでも新しいコンパイラを完成させることができませんでした。

機能の不足

Rubyで記述されたコンパイラでは、Array、Hash、Setを使用しました。ファイルシステムにアクセスし、LLVMへのバインディングを使用しました。これらの機能が言語と標準ライブラリにない限り、Crystalでコンパイラを実装することはできません。

そこで、標準ライブラリに多くの機能を追加しました。Cへのバインディングを追加しました。Cの構造体と共用体があります。関数ポインタがあります。これらはすべてCrystalで指定されており、別の言語で記述する必要はありません。

しかし、それでも…

バグ

コンパイラは完璧ではありませんでした。(そして、今もバグがあります)。小さな例やテストではうまく動作しましたが、Crystalでコンパイラを作成することで、Rubyで記述されたコンパイラが実際に動作し、多くのバグや不足している機能が明らかになりました。

そこで、これらのバグの大部分を修正し、不足している機能を追加しました。

しかし、これらすべてが常に意味していたのは

コンパイラの後れ

Rubyで記述されたコンパイラで導入したバグ修正や新しい機能は、(まだ正常にコンパイルされていない)Crystalで記述されたコンパイラに移植する必要がありました。そして、時折言語に新しい機能を追加しようとする誘惑があり、実際に追加したので、ますます遅れ始めました。

そこで、ある日、機能フリーズ(そして、回避策がない限り、バグフリーズも)を行うことにしました。

長い道のりでしたが、非常に興味深く、有益なものでした。

  • RubyからCrystalへのコードの移植は非常に簡単で、ほとんどの場合、わずかな変更で済みます。
  • 移植されたコードは、Rubyの場合とまったく同じ動作をしました。Rubyの構文とセマンティクスをこれほど正確に捉えることができたことに、私たちは今でも驚いています。
  • 移植されたコードは、Rubyで記述されたコンパイラにバグがあることを明らかにしました。つまり、理論的には、Crystalはより堅牢で正しいコードを作成するのに役立ちます。

そして今日、木曜日、ついに成功しました。Crystal自身で記述されたCrystalのコンパイラを作成することに成功しました!やった!新しいコンパイラは自分自身を正常にコンパイルでき、この新しいコンパイラは自分自身をコンパイルでき、結果として得られるバイナリは古いものとまったく同じです。また、その仕様もコンパイルでき、すべてパスします。

Crystalの未来は明るい

高速化(しました)

Rubyで記述されたコンパイラは、コンパイラの型を推論するのに約20秒かかります。Crystalで記述されたコンパイラは、同じ作業に約2.8秒かかります。

覚えておいてください。ここではグローバル型推論について話しています。コンパイラのセマンティック解析に2.8秒。全く悪くないでしょう!

そして、コンパイラによって生成されたコード、コンパイラと標準ライブラリに存在するコードの両方に対して、まだ多くの最適化を適用する必要があります(たとえば、Hashは非常にナイーブな実装です)。

もうRubyに依存しません

Crystalで記述されたコンパイラができたので、Crystalを使ってコンパイラの新しいバージョンをコンパイルできます。さようなら、Ruby。あなたをチームに迎えることができて嬉しかったですが、まあ、私たちのニーズには少し遅すぎました。ええ、あなたの多くの点は気に入っていますが、これはコンパイラの話です。プログラマにプログラムのコンパイルに何分も待たせるわけにはいきません。ああ、もしかしたら少し厳しすぎるかもしれませんね。ご存知ですか?いなくならないでください。戻ってきてください。Railsの仲間と一緒に素晴らしいWebアプリケーションのフロントエンド開発を支援し続けることができます。本気でそう言っています。あなたはそこで輝いています。しかし、バックエンドについてはそれほど自信がありません。ErlangとGoは本当に素晴らしいですね。構文(とセマンティクス)があなたのようには良くないのが残念です。いつか、あなたのような言語ですが、ネイティブコードのように高速な言語が登場するでしょうか?おそらくないでしょう。しかし、Rubyから移行してきた場合、わずかな犠牲を払う必要がある類似の言語はどうでしょうか?本当にそう願っています。

ロードマップ

コンパイラの残りのバグを修正する必要があります。バグのあるソフトウェアは好きではないので、堅牢なものにしない限り、言語に機能を追加し続けることはありません。

その後、並行処理、より良いマクロ、より良い関数ポインタ、真の構造体、名前付き引数、タプル、ファイバー、デバッガーについて考え始めることができます…