「RubyでつくるRuby ゼロから学ぶなおすプログラミング言語入門」を読了しました。
非常にチャレンジングな本で、なんとRuby初心者の方に向けてRubyを動かすプログラム、つまりRubyインタプリタを作ってみようという本です。「え、そんなのプログラミング初心者にできるの!?」と気になり、いつのまにかポチっていました(つまり衝動買い)。
今回は「RubyでつくるRuby」の書評をしつつ、どんな方にオススメできるか紹介していきます。
RubyでつくるRuby ゼロから学びなおすプログラミング言語入門www.lambdanote.com
本当にRubyインタプリタが作れるのか
はい、作れてしまいました。
僕はWEB系の開発エンジニアとして5年ほど働いていますが、大学で情報を学んだ訳ではありませんし、コンパイラや言語処理系の知識は全くありません。そんな自分でもRubyのインタプリタを作ることが出来ました。最後、自分が作成したRubyインタプリタでRubyで作成したFizzBuzzのプログラムが動いた時は「おおおおぉ!!!」となってしまいました。
ボリュームとしては100ページほどの書籍で技術書としては薄い本だと思うのですが、これだけでRubyインタプリタが作れてしまうというのは本当に驚きました。著者の方には敬意しかありません。
どうやって作るのか
Rubyインタプリタを作るにあたって面倒なのは構文解析を行なって、抽象構文木(AST)というデータを生成する処理です。簡単に言えばsample.rb
に書かれたプログラムを読み取って余計な情報を削ぎ落として処理しやすいデータ形式に変換するステップになります。構文解析によってプログラミング言語の文法は決まるといって良いでしょう。
この面倒な処理は、著者の方がライブラリを作成・公開してくれているおかげで自分で実装をする必要がありません。実装するのは出力された抽象構文木(AST)を順に処理するプログラムのみです。
p(1 + 2)
を抽象構文木(AST)に変換すると...
["func_call", "p", ["+", ["lit", 1], ["lit", 2]]]
抜粋: 抽象構文木(AST)を処理するプログラム
def evaluate(tree) case tree[0] when 'lit' # literalの略 tree[1] when 'func_call' : when '+' # 再帰的に実行されて1 + 2となる evaluate(tree[1]) + evaluate(tree[2]) end end
作成するプログラムは木構造のデータを扱うため再帰関数となっており、様々な処理に対応するためにcase
で大量の条件分岐を行います。
どんな方にオススメできるか
オススメできる人
- プログラミング言語を作成してみたい人
- 再帰関数について名前ぐらいは聞いたことがある人
- Rubyなどプログラミング言語を書いたことがある人
- Rubyで何か作ってみたい人
オススメできない人
プログラミングを始めるなら、プログラミング言語を自分でつくってみるのがいちばん! 最低限の機能なら、こんなに簡単にインタプリタを作れます。よくわからなかったプログラミングも、裏側の仕組みから分かってしまえば怖くない!
書籍のHPより引用
...とHPにはありますが、この書籍をプログラミング未経験者の方が取り組むには少しハードルが高いかなと感じました。特に再帰関数を使って木構造のデータをゴリゴリ処理していく部分は難しいと感じるかもしれません。最低限、何かしらの言語を触ったことがある、配列や連想配列、関数について知っている、分岐・繰り返し処理を書いたことがあるぐらいの理解が必要でしょう。
最後に
良い書籍と出会えて、久しぶりにワクワクするコードを書くことができました。著者の遠藤さんには感謝と敬意しかありません。本当にありがとうございます。
「自作の言語ってどうやって作るんだろう」と長年、疑問に感じていたのですが「RubyでつくるRuby」を読んだことで疑問が解消されました。作成したインタプリタにクラスを実装したりとまだまだ遊べそうです。構文解析についても興味が沸きました。
非常におすすめの書籍です。サクッと読めるのでぜひ読んでみてください。
少しでも「ええな〜」と思ったらイイネ!・シェア!・はてなブックマークを頂けると励みになります。
おまけ: ElixirのAST
以前、Elixirで黒魔術を学んだ時にquote do: #ここにプログラム
とすると、抽象構文木(AST)が出力されるということを経験していたのですが、今思えば「これは構文解析がされた状態だったんか!」ということに気づきました。
iex(2)> quote do: 1 + 2 {:+, [context: Elixir, import: Kernel], [1, 2]}
つまり、同じ要領でElixirのインタプリタが作れそうです。簡単な四則演算しかできませんがマジで作れてしまいました。関数型のパラダイムを持つ言語であればパターンマッチを使うことで、簡潔なコードを書くことが出来ますね。
defmodule Interp do def evaluate({ :+, _module, args }), do: left(args) + right(args) def evaluate({ :-, _module, args }), do: left(args) - right(args) def evaluate({ :*, _module, args }), do: left(args) * right(args) def evaluate({ :/, _module, args }), do: left(args) / right(args) def evaluate(val), do: val def left(args), do: Enum.at(args, 0) |> evaluate() def right(args), do: Enum.at(args, 1) |> evaluate() end ast = quote do: 1 + 2 + 3 Interp.evaluate(ast) |> IO.inspect() # 6