ゆーすけべー日記

Go言語でつくるインタプリタがグランドフィナーレを迎えました

Posted at — Mar 11, 2020

いよいよ「Go言語でつくるインタプリタ」がグランドフィナーレを迎えた。

そして、これで終わりだ。やり遂げた。これまでは私が誘う小さなお祝いを軽くあしらってきたとしても、いよいよ愉快なパーティ帽を被ってよい時間だ。

抜粋:: Thorsten Ball “Go言語でつくるインタプリタ”

やったー。パーティーだ!

GO言語でつくるインタプリタ

なんでこの本を読んだの?

今までコンピュータサイエンをまともに学んだことがなかった。 大学生の時分、授業では「プログラミング」と銘打つものがあるくらいで、 Javaでアプレットを作ったり、Cでシステムプログラミングをしたりという程度だった。 あとは独学。あくまで目的達成のための学習。 GUIやWebアプリの高レイヤーの実装ばかりで、 中がどうなってるのかあんまり理解できてなかった(いわゆる電子工作は好きだったけど)。 で、なんとかやってきたんだけど、 コンピュータサイエンスを学んでいないことはちょっとしたコンプレックスだった。 それに興味も湧いていた。

学校で学べなかったのなら、本を読んで勉強すればいいと思いいくつかの書籍を漁っていると、 「Go言語でつくるインタプリタ」に出会った。deeeet君が読んでる(彼は原著!)のも後押しになった。

インタプリタを実装するなんて楽しそうじゃないか。

「Go言語でつくるインタプリタ」

本書は「Writing An Interpreter In Go」を日本語訳した書籍。 オライリーから出版されている。 翻訳は(あとから気づいたんだけど)darashi君がやっている。

名前の通り、Go言語を使って「オリジナルの」プログラミング言語のインタプリタを実装していく。

私が欲しかったのは、900ページにも及ぶコンパイラについて書籍と、50行のRubyコードでLispインタプリタを実装する方法に関するブログ記事との間にあるようなものだ。

抜粋:: Thorsten Ball “Go言語でつくるインタプリタ”

実際300ページほどで、学ぶのに「ちょうどよいサイズ感」を目指している。

実装するのはMonkey言語。 これが「自分で作れちゃうのか!」というほど高性能で以下の機能がある。

よって、以下のコードが動く。

let me = { "name": "yusuke", "age": 38 };
puts(me["age"] + 1)

let newGreeter = fn(message) {
  return fn(person) { puts(message + " " + person["name"]); }
};
    
let hello = newGreeter("Hello");
hello(me);

このMonkey言語を字句解析から始まり、構文解析、評価と順を追って実装していく。

毎日少しずつやった

解説を読んで記載されているコードを書くってのを毎日少しずつ、1節もしくは1項単位で進めていった。 動作可能なコードがほぼ全部掲載されているのがありがたい。 最初は全体像を把握できていなくて、 「何言ってるのか分からない」ところもあったけど、 コードを書くと「なるほど」となった。 ASTを書く頃にはレキサーが何のためにあるのか分かったし、 評価機の頃にはASTの扱い方が分かってきたし、 文字列や配列、ハッシュなどのオブジェクトを表現する段階までくるともう楽してくしょうがなくなった。

loading... loading...

ちなみに、オライリーのサイトから電子書籍を買ったので、 epubをiBooksに取り込んで、 Macで表示してそれを見ながらコードを書くというスタイルがうまくハマった。

GO言語でつくるインタプリタ

コードはGit/GitHubで管理した。

よかったところ

よかったところをいくつかあげる。

まず、語り口調のそれがたまらない。 著者の熱意とユーモアにあふれている。 これはdarashi君の翻訳の妙が多分にあると思う。 「Monkey」という言語の名前も好きです。 冒頭で引用したセリフもそうなんだけど、各節目が終わるごとにテンションが高くて、 それを体験したいがために書いている感じもしてくる。

“やった。ついに完動するMonkeyインタプリタができあがった。関数も、関数呼び出しも、高階関数も、クロージャもサポートしている。さあ、お祝いしてきてくれ! 私はここで待っていよう。”

抜粋:: Thorsten Ball “Go言語でつくるインタプリタ”

著者の方も最初からインタプリタやコンパイラの中身を知っていたわけではなく、 「一体どういう仕組みで動いてるんだろう」という興味を持っていたらしい。 そして自分のため、読者のために本書を書いたとのこと。 なにかの役に立つなんて考えてなくて、ただ学ぶためにやろう、っていうスタンスがよかった。

内容的なところでよかったなと思うのは、REPLからまずつくるって点。 レキサーを書く第1章でもうREPLを実装する。 REPLがあると、書いてそれがどう動くのかが逐一確認できて楽しかった。

GO言語でつくるインタプリタ

Goで書くってのもよかった。 シンプルだからいいってのもあるけど、 例えばgofmtで整形が統一されているので、 書籍のコードと自分が書いたコードのインデントなりが完全に一致して気持ちがよい。 静的型付けで、それをある程度柔軟に操作できることもポイントだ。 例えば、ハッシュのキーは内部でGoのStringIntegerBooleanを使うんだけど、 キーを表現するためにそれぞれにダックタイピングなメソッドを生やしたりしていて面白かった。

type HashKey struct {
    Type  ObjectType
    Value uint64
}

...

func (i *Integer) HashKey() HashKey {
    return HashKey{Type: i.Type(), Value: uint64(i.Value)}
}

func (s *String) HashKey() HashKey {
    h := fnv.New64a()
    h.Write([]byte(s.Value))

    return HashKey{Type: s.Type(), Value: h.Sum64()}
}

テスト駆動なのもよかった。 最初に期待する動作をテストに書くわけだから、 これからどんな実装をすればいいのかが分かる。 Goというプログラミング言語のテストの中の期待する値にMonkeyというプログラミング言語のプログラムを書くってのが 新鮮だった。

func TestBuiltinFunctions(t *testing.T) {
    tests := []struct {
        input    string
        expected interface{}
    }{
        {`len("")`, 0},
        {`len("four")`, 4},
        {`len("hello world")`, 11},
        ...
    }
    ...

ところで、こういうプログラムを紹介する書籍ではとりわけテストから先に書くというのはすごく有効だなって思った。

まとめ

実は最初のコミットからおおよそ1ヶ月半経っている(普通の人はもっともっと早く終ると思う!)。 これでインタプリタを実装できたわけだが、 それはほぼ写経みたいなもので自分が1から書いたものじゃない。 でもやっていることは分かったし(コンパイラはプログラムの字句解析から始めるのだ!)、 何よりもとっても楽しかった。 コンピュータサイエンスの序の口を突破したかな?

まだ付録にあるマクロの実装を残しているけど、 グランドフィナーレという区切り付いたので、 次の書籍に移ろうかなって思う。 難しそうなところだとNand2tetrisをやる「コンピュータシステムの理論と実装」もいいし、 簡単そうなところだとRubyでインタプリタをつくる「RubyでつくるRuby」も面白そう。 でも、その前にパーティーをしよう!

Go言語でつくるインタプリタ amazon.co.jp
comments powered by Disqus