Ruby のしくみ を読んだ

なぜ読んだか

メタプログラミング Ruby を読んだ ので、この勢いで Ruby のことをもっと深く知りたかった。

どうだったか

エキサイティングな体験だった。

この本には RubyRuby コードをどうやって実行しているのか、ということが書いてある。字句解析してトークン列に変換して、構文解析してAST(抽象構文木)ノードに変換して、コンパイルして YARV 命令列に変換して、それを YARV(仮想マシン) が実行する....どうやって?ということが書いてある。

登場する Ruby のコードは単純なものばかりだが、その背後にあるしくみ、C のコードは複雑だ。にもかかわらず、この本を読んでいると自分がそれをいとも簡単に理解したかのような錯覚に陥るので爽快感がある。ワクワクする。それはきっとこの本の著者の説明が驚異的に上手なのと、豊富な図のおかげだろうと思う。

著者の Pat Shaughenessy さんのサイトを見ると、やっぱり図が豊富で驚く。

いくつか特に印象に残った内容について書いてみたい。

コンパイラがあるのは Ruby 1.9 から

Ruby 1.8 までコンパイラはなく、Ruby コードを実行するときは AST を直接実行していたということを知った。Ruby 1.9 からはコンパイラがある。コンパイルすると Ruby コードを実行するまでに一手間増えることになるけど、これまでよりずっと高速に実行できる。

コンパイルして中間言語を生成して、それを実行するというのがどういうことか、これまでより具体的に知れた気がする。

スタックの話

YARV は2つのスタックを持つということを知った。値、引数、返り値を把握するための内部スタックと、どのメソッドが他のメソッド、関数、ブロック、ラムダなどを呼び出したか把握するためのコールスタックの2つ。

このあたりの仕組みがとてもおもしろかった。Rubyメソッド、ブロックなどの単位で rb_control_frame_t 構造体を作ってスタックに保持するということや、それがコールスタックになるということ。その構造体に含まれているポインタの話や、内部スタックに積まれている変数にどうやってアクセスするかという話など、どれもワクワクした。おそらくコードを読んでもわからないであろうことをすらすら説明していて、図と一緒に読むことでぼんやりわかった気になれるというのが、この本の魅力だと思う。

で、このあたりの話でどのポインタが一番好きかというと、EPが好きだった。

  • PC - プログラムカウンタ。現在の命令の場所を指す
  • SP - スタックポインタ。内部スタックの一番上の場所を指す
  • EP - 環境ポインタ。現在のメソッド用のローカル変数の場所を指す
  • CFP - 制御フレームポインタ。現在の制御フレーム(rb_control_frame_t構造体)を指す

異なるスコープの変数にアクセスするときにEPをたどっていくという話がよかった。スタックが積まれるとEPの値も変わってしまうので、じゃあどうやってスコープ外の変数にアクセスするの? --> EPを辿れるのである --> おもしろすぎる....という感想を持った(伝わらなそう)。それに特殊変数($ から始まる変数)はEP-1の場所にあるんだよというのもなるほど感あった。これだけでも楽しいのだが、あとでクロージャの話で再び EP が出てきて、それがまたシビレるのである。

多重継承の話

Rubyメソッドをどうやって見つけるかという話で、メソッドが見つかるまで super ポインタをたどっていくだけで単純というのがあった。内部的にはクラス継承をつかってモジュールのインクルードを実現しているので、モジュールに定義されているメソッドでも super ポインタをたどっていけばよい。

では Ruby コードを書く側はクラスの継承とモジュールのインクルードをどう使い分ければいいのか?という疑問を持った。その時に考えたのはこういうことだ。

  • オブジェクトの is_a 関係に着目するときは継承
    • Book クラスは ActiveRecord オブジェクトであるとか
  • メソッドの実装に着目するときはインクルード
    • クラスに機能を追加したいというとき
    • これをクラス継承でやると継承ツリーが深くなってしまうかも
    • そうすると読みづらいし、実装が継承されていると変更しにくいコードになる

そのあとインターネットでこの記事を見つけて読んでいた。いい記事。

まつもと直伝 プログラミングのオキテ - まつもと直伝 プログラミングのオキテ 第3回(3):ITpro

クロージャの話

1975年に Sussman と Steele という人によってクロージャが定義された。そのクロージャとは、ラムダ式と、そのラムダ式が引数に適用される時に使われる環境の両方を持つデータ構造、ということらしい。Ruby ではブロックがクロージャで、ブロックを表す rb_block_t 構造体を見ると、その定義と一致する。

  • iseq はラムダ式へのポインタ - 関数あるいはコード片
  • EP はラムダを呼び出した際に使われる環境へのポインタ - つまり周囲のスタックフレームへのポインタ

なるほど〜。

あとはメタプログラミングの章で、eval の第2引数に渡すバインディングオブジェクトの話がでてきて、このバインディングというのは関数なしのクロージャ、つまり環境を意味するんだよというのを読んで目から鱗が落ちたのであった。

ちょっと前の話。Rubypry というライブラリがあって、ソースコード中に binding.pry と書いてからコードを実行するとそこで pry が起動する。この binding.pry というのを何も考えずに使っていたんだけど、メタプログラミング Ruby を読んでいる時に Binding オブジェクトというものがあることを知って、binding というのはそれのことか!と思い知ったばかりだったので、それがさらにこういう話につながっていくのを見ると、なんというか、たのしい体験だった。

まとめ

Ruby のしくみ、おもしろかったです。ありがとうございました!!

Rubyのしくみ -Ruby Under a Microscope-

Rubyのしくみ -Ruby Under a Microscope-