Writing An Interpreter In Go その8
その7はこちら
3.10: 関数の定義と呼び出し
3.9: Implement function call · nibral/monkey_interpreter@f09f2fd · GitHub
(コミットメッセージの章番号を間違えてた)
関数を定義する fn 構文と、定義した関数を呼び出す処理を実装する。本に従ってコードを書いていくと、構造体の定義、関数の処理と引数の取り出し、関数の評価という順で一つずつ進んでいく
ここでのポイントは2つ。
1つ目は関数実行時に Environment(変数空間) を入れ子構造にすること。下の例のように定義した関数の外側と内側で同じ変数名が使われる可能性があるので、関数呼び出しを評価する時にはプログラム全体の Environment (outer) とは別に関数内部でのみ使用する Environment (inner) も使う必要がある。
引数の値はinner側に追加する、innerに指定された識別子がなければouterを見に行く、という動作にすることでブロックスコープが実現できる。書いて動かしてみるとシンプルかつスマートな感じがした。
let i = 5; let printNum = fn(i) { puts(i); }; printNum(10); puts(i);
2つ目は ReturnValue を返さないようにすること。式を評価した結果として ReturnValue を返すとそこでプログラムが終了するようになっているので、関数内部でreturn文が使われたときはReturnValueの値を取り出して(unwrapして)評価値とする必要がある。
let fooFunc = fn() { return 5; } fooFunc(); // ReturnValueをunwrapしないとここで終了してしまう return 10;
3.11: メモリ解放の話
Q. 明示的なメモリ解放をしていないので、関数の再帰呼び出しが深くなると その分メモリを消費したままになりますがどうしたらいいですか A. golangのガベージコレクション (GC) がよしなにしてくれるのでそれでよしとしましょう
ということでコードの追加はなし。
ともあれ、これでEvaluatorも完成。関数という一見複雑そうな機能を付けたわりに、追加したコードの量はそこまで多くなかったように感じる。
インタプリタ本、単なる関数定義と呼び出しだけかと思ったら高階関数まで使えるようになって強い pic.twitter.com/LYJkgNqonX
— あみだ (@_nibral) 2019年1月28日
本の目次を見ると、残る4章では新しいデータ型として文字列・配列・ハッシュを追加するらしい。このシリーズもあと3回くらいで終わるか?