今までyuniはR6RS/R7RSの互換ライブラリということにしていたが、これにR5RSを加えられないか検討することにした。

本家Schemeであるところのmit-schemeがR5RS

Bones(amd64のアセンブリを吐くコンパイラ https://bitbucket.org/bunny351/bones/ )とかBiglooのような興味深い処理系が有る

Alexpanderを使えば、BiwaSchemeとかtinyschemeのようなdefine-syntaxマクロの無い処理系にも対応できる

で、実際に試してみたらそれなりに動いたので、とりあえずサポートに加えてみて実際のアプリケーションで検証を進めていく。

ライブラリ機構が無い → ソースコード連結器を書く R5RSの言語自体にはライブラリ機構が存在しない。というわけで、既存のPicrin用のライブラリローダを流用して適当なローダーを用意した。https://github.com/okuoku/yuni/blob/adccf4a840243a9111b88ded27e3313e26906e7c/yuniloader/yuniloader-fake.scm

このローダーはプログラムのimport節を読んで必要なライブラリファイルをロードし、それぞれのライブラリファイルのimport節も再帰的に処理してプログラムに必要なライブラリを全部連結したS式を出力する。

S式の出力過程で、ライブラリは多少変換される。基本方針は過去のにっきに書いた( http://d.hatena.ne.jp/mjt/20150601/p2 )もので、 基本的にライブラリは全体をletで囲んで定義が外に出ないようにする

exportしている変数は適当にrenameしてletの中からset!する

define-syntaxした定義をexportしている場合はtop-levelにご招待 define-syntaxした定義をexportしているライブラリがimportしているライブラリもtop-levelにご招待

マクロのrenameは禁止 というルール。 ( library ( a ) ( export a ) ( import ... ) ( define a 10 )) ( library ( b ) ( export b ) ( import ( a ) ... ) ( define b 10 )) ( library ( c ) ( export c ) ( import ( d ) ... ) ( define-syntax c ... )) ( library ( d ) ( export d ) ( import ... ) ( define d 10 )) ↓yuniloader: 別々のファイルにあるものを集めて1つのR5RSプログラムに合成して出力 ( define %%a #f ) ( let () ( define a 10 ) ( set! %%a a )) ( define %%b #f ) ( let (( a %%a )) ( define b 10 ) ( set! %%b b )) ( define %%d #f ) ( let () ( define d 10 ) ( set! %%d d )) ( define d #f ) ( set! d %%d ) ( define-syntax c ... ) 実際のyuniのCIテストを変換してみたもの: https://gist.github.com/okuoku/9152a66a1051a68b2677bfbe7b3650b0 (かなり下の方にスクロールしないとimportが有るライブラリに辿りつかない)

変数のエクスポート処理はset!で行う。マクロはtop-levelにdefine-syntaxするしか無いので、特にエクスポート処理やインポート処理は無い。letで囲めるライブラリの場合はインポート処理はletのバインディングとして行えるが、toplevelにご招待されたライブラリは、エクスポートと同様のdefine - set!ペアでインポート処理を行う。R6RS/R7RSのライブラリ機構と違い、toplevelにご招待されたライブラリのグローバルシンボルは後続のライブラリのグローバル環境からも見えてしまう。

実際のアプリケーションでは、ライブラリからマクロをexportするケースはかなり稀で、仮にマクロをexportしたとしても、多くのライブラリをimportしていることはほぼ無いため、これらの規制が有ったとしても特に不都合は無さそう。

ローダはライブラリをreadし、トップレベルのシーケンスをスキャンしてdefine-syntaxを探し、トップレベルでdefine-syntaxしているものはマクロであると認識する。define-syntaxに展開されるマクロがexportされているケースが有ったらどうすんの(= 字面からだけではマクロと認識できないケース)という問題は有るが、手元のコードではそのようなマクロは2例しか無かった。

yuniは今でも補助構文(...とか=>やelse)のrenameは禁止している。これは補助構文がboundでない処理系がR6RS/R7RSにも普通に存在し(ChickenやGuile)、互換性の確保が難しいため。R5RS環境を勘定に入れるなら、補助構文だけでなくマクロのrenameも禁止になる。

"toplevelにご招待"とは、単にlibraryで囲まれた部分をそのまま出力するだけとなることを指す。このため、マクロをexportしているライブラリ内部のdefineは全部グローバル変数になる。ただ、微妙に盲点だったのは、マクロをexportしているライブラリに直接importされる関係に有るライブラリもtoplevelにご招待される点。このためライブラリを設計するときにマクロを別ライブラリに分離するといった配慮が必要になる。

defineのセマンティクスが違う → あきらめる 個人的にSchemeを本格的に使い出したのはR6RSからなのでR5RSの仕様にはあまり詳しくない。が、今回初めてガッツリとR5RSを書いてみて一番壁になったのはdefineの仕様がletrec*でなくletrecである点。これはもうどうしようもない。

R6RS/R7RSでは、internal defineはletrec*で定義される。このためdefineの定義は上から順番に確定すると考えることができる。 ( define a 10 ) ( define b ( + a 10 ) ↓ ( letrec* (( a 10 ) ( b ( + a 10 )) ... ) R5RSでは基本的にletrecの挙動が採用されている。なので、 ( define a 10 ) ( define b ( + a 10 )) ↓ ( letrec (( a 10 ) ( b ( + a 10 )) ... ) この挙動はyuniの方々で依存しているところがあったので、ライブラリを分割する等して対応した。 ( library ( a ) ( export a ) ( import ... ) ( define a 10 )) ( library ( b ) ( export b ) ( import ( a ) ... ) ( define b ( + a 10 )) psyntax-moshからnmoshに移植するときに、同じような理由でライブラリを分割する必要があったのを思い出すな。。