Maestumプログラミング、囲碁、読書の話題この広告は、90日以上更新していないブログに表示しています。前回から続いて、REPL機能を追加し、変数と関数を定義できるようにする。Loopの略で、対話的にプログラムを入力・実行できる環境である。この場合はLisp>>>などといったプロンプトを表示してS式を受け取り、評価して結果を返した上で再度プロンプトを表示するようにする。終了のためにはquitと入力すればいい。github.com変更はbin/ex.mlファイルだけ。()例外を投げっぱなしにしていたのをcatchして文字列として出力するようにした。しないとループが止まってしまう。よさげな書き方がありそうなものだが・・・REPL自体は再帰関数となっていて、それでループしている。第七章では変数を定義できるようにする。そのためには変数環境を作る必要がある。このチュートリアルでの変数はlet式によるイミュータブルなものではなく、defineやset!で破壊的代入を起こすものなので、それに合わせて変数環境もミュータブルな状態を持つデータ構造にする。Hashtblで実装してみる。e)基本的にはHashtblの関数をラップする形でEnvの関数を定義している。xは変数xが束縛された値として評価される。set!やdefineは変数環境を変更して、束縛された値を評価結果として返す。())fやreplがenvをとるようにし、プログラムスタート時に新しく変数環境を作ってreplに渡す。y)9のように変数を定義・代入して使える。第八章はユーザが関数を自作できるようにする。defineで直接名前に束縛する・lambdaで無名関数として定義する、といった選択肢を用意し、またmultiarityにも対応する。以前定義したprimitivesもある程度統一的なアーキテクチャで呼び出されるようにしたい。github.comまずは変数環境を見直すところから。いうのもこのLispの関数はclosureを持つので、関数が定義された時点での変数環境を凍結させた形で関数のデータとして保持させたい。そのためには完全にミュータブルなHashtblだと少し都合が悪い。関数定義を実装するためには、ExpとEnvの型を相互再帰にする必要が出てくる。refになっており、式の型はFuncが追加され、その内部ではclosureにenvt型が使われている。EnvとExpはこのEnvexp内の型定義を使って書かれている。Evalに長々と書かれていたprimitive関数の定義はすべてPrimitivesモジュールに移し、変数環境に全部定義するための関数loadを用意しておく。Primitives.load)などとしてプログラムスタート時にPrimitivesを変数環境に加えている。あとはEvalモジュールで関数呼び出しと関数定義に関するコードを追加する。func)先頭の要素がquoteやifなどの特殊なワードではない場合、まず先頭要素を評価し、それ以外の要素を評価し、先頭要素を関数、それ以外を引数としてapplyする。先頭要素がPrimitive関数な場合はそのまま引数を渡して結果を計算する。primitiveではない関数な場合、関数のclosureに仮引数と引数を対応させる形で追加し、そのclosureをenvとして関数のbodyとなるリストを順次評価(ここでapplyとEval.fが相互再帰する)、最後の結果を式全体の評価結果として返す。fn.closureとくにmultiarity用のvarargsの存在が実装を少しややこしくしている印象。*)varargs有無でdefine/lambdaによる関数定義ができる。incinc変数はmy-counterの中には存在していて、関数呼び出しごとに破壊的代入されている。関数の外からはアクセスできない(外側の変数環境には存在しない、とエラーが出る)。ここまででようやくちゃんとしたプログラミング言語らしくなってきた。再帰ができないのが非常に気がかりだが、とりあえず48時間以内に最低限のLispが実装できた、と言ってもいい気がする。