Thorsten Ball : Go 言語でつくるインタプリタ

作成日:2020-09-06
最終更新日:

概要

Go 言語でインタプリタを作成する。スクラッチでプログラムを作ることで、 プログラミング言語とインタプリタのしくみを実践的に学ぶことができる。

インタプリタを実装するための言語について

本書で作って見せるインタプリタは Monkey というなまえの言語である。 この Monkey を作るための言語として、本書ではプログラミング言語 Go を用いている。では、Monkey を作るには、ほかの言語でできるのだろうか? 「はじめに」の xii ページで次のように著者は言っている。

それにも増して重要だと思うのは、本書で提示する Go のコードは、 他の言語でもほどんと同じように書けるということだ。 おそらく C や C++、Rust のように、 より低レベルな言語でも大丈夫だ。

私はこのことばを信じて、本書を理解したら C で Monkey を作ろうと思ってしまった。 ところが、「3.11 ゴミを片づけているのは誰か」の節を読み、がっかりしてしてしまった。 というのも、Monkey のガベージコレクタ(GC)は Go 言語の GC を使っている、 ということが書かれていたからだ。 そして、これを受けて p.173 では次のように注意している。

もし C のような言語でインタプリタを書こうとしていたら、 C には GC がないので、自分自身でそれを実装して、 インタプリタのユーザのためにメモリを管理する必要がある。

ということは、事実上 C や C++ で Monkey を実装するのは不可能ではないか、と思った次第だ。 もちろん、Boehm GC ライブラリを使うという方法はあるだろうが、 意欲が一度に下がってしまったことも事実である。

なお、この Monkey を Rust で実装した方々がいる。Rust の資源管理は GC とは別の手法によるようだが、実装できているようなので、うまくやっているのだろう。

途中のコンパイルについて

p.92 でテストを行ったところ、本とは異なるエラーが出てきた。これは、 テストの失敗以前に、コンパイルエラーとなっている個所である。

$ go test ./parser
(中略)
parser/parser_test.go:216:42: tt.integerValue undefined (type struct { input string; operator string; value interface {}} has no field or method integerValue)
parser/parser_test.go:288:41: cannot use tt.leftValue (type interface {}) as type int64 in argument to testIntegerLiteral: need type assertion
parser/parser_test.go:297:42: cannot use tt.rightValue (type interface {}) as type int64 in argument to testIntegerLiteral: need type assertion
(後略)

結局、これらのエラーの出現個所での関数呼び出しをコメントアウトするしかなかった。 本書に記載されている、アーカイブからダウンロードされたソースコードをみると、 該当部分の記載がない。テストに応じて、記載をしたり省いたりということなのだろう。

恥ずかしい話

私も、ほかの方々と同様、本にあるコードを手で打ちながら、 動作を確認した。私の動作環境は、Ubuntu (正確には Windows Subsystem for Linux の Ubuntu 20.04) の、go1.13.8 linux/amd64 である。 本書の通りに入力したはずなのに、どうしても同じように動作しないという個所がいくつかあったが、 よく見ると私のタイプミスだった。よくあったのが () と {} の打ち間違いである。 私は Go 言語の文法を全く知らないので、この打ち間違いはよくあった。

一つ恥ずかしかったのは、4.2 文字列の 4.2.3 文字列の評価のところだ。 本書の通り入力したのだが、結果が異なる。まず、正しく失敗するはずの個所だ。pp.181 では次の通り。

$ go test ./evaluator
--- FAIL: TestStringLiteral (0.00s)
  evaluator_test.go:317: object is not String. got<nil> (<nil>)
FAIL
  (後略)

一方、私の結果は次の通り。

--- FAIL: TestStringLiteral (0.00s)
  evaluator_test.go:330: object is not String. got=*object.Error (&{Message:identifier not found: Hello})
FAIL
(後略)

まあ、同じ失敗だし、いいか、と思っていたら、全くよくないのだった。その後、 evaluator.go を更新して完成させても、私の環境ではエラーのままである。 一晩明けて、本書を見直してようやく私の失敗がわかった。本書では、テストコードにこう書かれている。

 input := `"Hello World"`

一方、私の入力は次の通りだった。

 input := "Hello World"

つまり、バッククォーテーションを抜かしていた。これでは誤ったエラーになるのも無理はない。

もうひとつ、こんなエラーもあった。 p.199 の最後のテストでエラーが出る。

$ go test ./parser
--- FAIL: TestOperatorPrecedenceParsing (0.00s)
	parser_test.go:440: expected="((a * ([1, 2, 3, 4][(b * c)])) * d)", got="((a * ([1, 2, 3, 4][(b * c)]) * d)"
	parser_test.go:440: expected="add((a * (b[2])), (b[1]), (2 * ([1, 2][1])))", got="add((a * (b[2]), (b[1], (2 * ([1,2][1]))"
FAIL

p.198 でテストが通らないとされた本書のエラーは、私が得たエラーと同じである。

    parser_test.go:393: expected="((a * ([1, 2, 3, 4][(b * c)])) * d)", got="(a * [1, 2, 3, 4])([(b * c)] * d)"

何がいけないのだろう。しばらくして、ast.go で次のように書くべきところ

func (ie *Index Expression) String() string {
	var out bytes.Buffer

	out.WriteString("(")
	out.WriteString(ie.Left.String())
	out.WriteString("[")
	out.WriteString(ie.Index.String())
	out.WriteString("])")
}

誤って次のように書いてしまったのだ。

func (ie *Index Expression) String() string {
	var out bytes.Buffer

	out.WriteString("(")
	out.WriteString(ie.Left.String())
	out.WriteString("[")
	out.WriteString(ie.Index.String())
	out.WriteString("]")
}

付録はマクロシステムである。どうにかこうにか入れ終えて、最後に到達した:
>> unless(10 > 5, puts("not greater"), puts("greater"));

本書にあるとおり、1行だけ greater と表示されることを予期していた私は、 少しずっこけた。表示は2行にわたっていた。

greater
null

でも、この null は出て当然のはずだ。というのは、 p.230 で、REPL では puts から期待される出力に加えて null も表示される。 と書かれているからだ。

私は1日を8時間ほど本書の写経に励んでいたが、写経速度が遅いことに加え、 打ち間違いによるエラーが続出し、結局すべてを稼働させるのに付録を含めて1週間を要した。 年寄りである。

なお、これだけ写経したにもかかわらず、Go 言語についてはほとんど理解できていない。

誤植

p.88 上から5行め、「真偽値リテラルを実装する方法ついて」とあるが、 「真偽値リテラルを実装する方法について」が正しいだろう。

p.242, evaluator/quote_unquote.go のソースコードでは、 冒頭にパッケージ宣言 package evaluator が抜けている。 同様に、p.247 , ast/modify.go のソースコードでは、 冒頭にパッケージ宣言 package ast が抜けている。

書誌情報

書 名Go 言語でつくるインタプリタ
著 者Thorsten Ball
発行日
発 行オライリー・ジャパン
発 売オーム社
定 価3400 円(本体)
サイズ判 ページ
ISBN978-4-87311-822-2
その他越谷市立図書館南部図書室で借りて読む

まりんきょ学問所コンピュータの部屋コンピュータの本コンパイラ・インタープリター > Thorsten Ball : Go 言語でつくるインタプリタ


MARUYAMA Satosi