picrin scheme開発メモ #5 srfiライブラリ 伝統的マクロ

週1ぐらいでかけたらいいなあと思いつつ全然書けていない…今週は小旅行や大学の課題などであまり時間が取れなかった。雑多な修正がメイン。

https://github.com/wasabiz/picrin

contrib

@stibear氏の多大なる貢献のお陰でsrfi-1が入った。感謝。他にもdefine-record-typeの実装、(scheme lazy)の追加、issueでの議論など、すべて氏の貢献によるもので、ありがたい限り。contributionといえば、つい数日前にredditにpicrinについて投げてみた。(http://www.reddit.com/r/scheme/comments/1y83jk/want_a_fast_portable_r7rs_scheme_try_picrin_scheme/) おかげでいくつかのissueを投げていただいたり議論へ加わってもらったりなどした。自分では殆ど書かないようなコードでまだコンパイラやランタイムにバグがあるようで、そういうものは新しい人に試してもらうことでしか洗い出せない。もっとも、テストケースの充実は可及的速やかに行われるべきではある…

伝統的マクロ

picrinができた当初からdefine-macroはデフォルトで提供されていた。(scheme base)の中に直接含まれており、REPLなら起動した時点で使える。しかし、これにはいくつか問題があって、

  • R7RSでは(scheme base)(や、他のR7RSで定められたライブラリ)から規定以外のidentifierをexportしてはならないことになっている
  • define-macroしか提供されていない。つまり、gensymやmacroexpandがない。

gensymとmacroexpandについては(CLとは若干仕様が違うものの、)Picrin内部のmacroexpanderですでに実装されていたので、それを公開APIにすればいい。exportについては、(picrin macro)というライブラリを新たにつくってその中にpicrin独自のマクロ拡張をすべて入れることにした。そんなわけで、今(picrin macro)ライブラリをimportすると以下のAPIが使用できる。

- `(picrin macro)`

    - `define-macro`
    - `gensym`
    - `macroexpand`

        Old-fashioned macro.

    - `make-syntactic-closure`
    - `identifier?`
    - `identifier=?`

        Syntactic closures.

    - `er-macro-transformer`
    - `ir-macro-transformer`

        Explicit renaming macro family.

Implicit Renaming Macro

マクロ展開器をリファクタリングした。今までマクロを束縛するidentifierの扱いが下手だったために一部のケースで健全性が破壊される問題があった。ついでにir-macro-transformerを実験的実装から正式なAPIに格上げした。今までの実装はsyntactic closureを使ったなんちゃって実装だったのでそれをついにC言語レベルで書きなおしてまともに動くようにした。チューニングは一切していないのであまりに巨大な式を展開しようとするとかなり時間がかかってしまうのだけれど普通に使っていれば問題ない。そもそもir-macroを使う以上展開速度の問題とはいずれにせよ付き合っていかないといけない。

変数のHoisting

ちなみに、マクロ展開器のリファクタリングの最中面白い事に気づいた。既存のマクロ展開器に存在したバグをつぶしたという話なのだけれど、今まで以下のプログラムは特になんの警告も出さずにコンパイルされていた。今では、double definitionの警告が出る。

(if #f 
    (begin (define a 1))
    (begin (define a 2)))

つまり、JavaScriptにある変数のHoistingが行われるということだ。JavaScriptを初めて勉強したときに「ああ、これSchemeでも起きうるじゃん」と頭ではわかっていたつもりだけど、これに実際に引っかかったケースは初めてなのでちょっとびっくりした。普通のschemeを書いている時は多分こんなコードは書くことはない。マトモなプログラマーならletを使うはずだから。じゃあなんでこんなコードを書く必要があったかというと、何の事はない、このコードが書かれていた場所はまさに、letマクロの定義その場所だ。

quasiquote quasiquote quasiquote

今までのPicrinはネストしたquasiquoteに対応していなかった。それを今回修正したのだけれど、なんでこんなに対応が遅くなってしまったのかというのには理由がある。実は、R7RSにはネストしたquasiquoteの扱いについて曖昧さ(というよりも、未定義部分)がある。それはなにかというと,,@expr,@,@exprの扱いだ。ネストしたquasiquoteの中でこれらの式があらわれると、展開形は(unquote expr1 expr2 ...)(unquote-splicing expr1 expr2 ...)のようになってしまう。そしてR7RSではunquoteやunquote-splicingが複数の引数をとった場合の処理について記述されていない。一方R6RSではこれらについてちゃんと仕様がはっきりしている。R7RSでなぜR6RSのやり方を受け継がなかったのかは議論を追っていないのでわからないけれど、もしPicrinでちゃんと実装するならR6RSに合わせたほうが良さそうだ。(現状では、単にこういう式はunquoteやunquote-splicingではないとして無視される。)

ちなみに、Common Lispではこういう問題は起こらないようになっている。というのも`や,、,@はCLではリーダーレベルで処理されることになっている。これはemacs lispでも同じで、この仕様のおかげでいわゆるLispでは以下の様なコードがかける。

`(foo . ,bar)

schemeではこれはリーダーによって(quasiquote (foo unquote bar))として処理されてしまう。追記:そんなことはなかった。

個人的にはリーダー依存のquatationルールを持つLispのunquoteはschemeのものに比べて覚えることが多くてやや複雑という印象がある。cf. http://qiita.com/snmsts@github/items/ef625bd6be7e685843ca