マクロの展開とシリアライズ

以外と地味なので見落しがちな点。。

(shorten)がでかい

今のshortenライブラリを展開するとすごいサイズになる。(nmosh 0.2.5はそもそも(shorten)を内蔵しているのでキャッシュには表われない。そのうちリリースする0.2.6からは外部ライブラリに戻した。)

prism:.nmosh-cache oku$ ls -alh | grep shorten
-rw-r--r--     1 oku  staff   476K 10  7 03:42 ...~shorten.ss.nmosh-cache
-rw-r--r--     1 oku  staff   499K 10  7 03:42 ...~shorten.ss.nmosh-cache.ndbg
-rw-r--r--     1 oku  staff   792B 10  7 03:42 ...~shorten~helper.ss.nmosh-cache
-rw-r--r--     1 oku  staff   1.5K 10  7 03:42 ...~shorten~helper.ss.nmosh-cache.ndbg

なんてサイズだ。
これは、shortenが不健全なマクロであることに由来する。
伝統的なSchemeのマクロは、マクロによって導入されるシンボルが存在しても、それは全てリネームされるため、マクロが起動された文脈に結果は依存しないという性質がある。
しかし、R6RSで書けるようになった不健全なマクロ、つまり、datum→syntaxを使ったマクロは、マクロが起動した文脈に結果が依存するため、マクロを定義した環境をマクロのコードと一緒に保存しておく必要がある。
極論を言えばこのようなマクロはlibraryのexportしていないシンボルすらアクセスできるので( http://d.hatena.ne.jp/mjt/20091229/p1 )libraryが何をimportしているのかという正確な情報を、"マクロが実行されるまで"保持していなければならない。
このような問題はonlyを書くことによって防ぐことができる。

(library (shorten)
         (export ...)
         (import 
           (only (rnrs) define-syntax lambda syntax-case define let begin with-syntax
                 symbol->string string->symbol string-append cons map cdr syntax                                                          
                 quote quasiquote unquote
                 datum->syntax)
...

このようにonlyを宣言することで、保存しなければならない環境のサイズを小さくすることができるため、

-rw-r--r--     1 oku  staff    97K 10  9 01:47 ...~shorten.ss.nmosh-cache
-rw-r--r--     1 oku  staff   128K 10  9 01:47 ...~shorten.ss.nmosh-cache.ndbg

キャッシュのサイズは劇的に減少する。
onlyを書かない場合は、キャッシュファイルには(rnrs)によって導入される全シンボルが記録されてしまう。このため、キャッシュファイルが巨大化する。

そもそも環境を保存する必要が有るのか: reflection safe macro

まず、(shorten)のようなケースでは、必要ないことは明確と言える。shortenによって導入されるシンボルは絶対に(rnrs)のexportしているシンボルと衝突しないことがshortenの仕様では保証されているため。(でもexpanderはそのような判断が行えないため、衝突が起こる可能性が有ると判断して、全ての環境を含めてしまう。nmoshの場合、datum→syntaxを使った場合は常に全ての環境を含める。)
このように、どのような入力が与えられてもライブラリの書かれた環境に展開結果が依存しないことをreflection safeと呼ぶことにする。(たぶん正確な呼び方が別に有るが、見つけられなかった。)
reflection safetyを破るためには、ライブラリの文脈を利用してdatum→syntaxすることで行える。

(library (access) 
         (export foo) 
         (import (rnrs)) 
(define login-name 'shibuya) 
(define login-password 'tokyo) 
(define-syntax foo
  (lambda (x) 
    (syntax-case x ()
      ((_ y) (datum->syntax #'z (syntax->datum #'y)))))))

(foo login-name) ;=> shibuya

この例では、(access)がexportしているシンボルはfooだけであるにも関わらず、login-nameというライブラリ内部のシンボルが出力できてしまう。
これは、datum→syntaxで使われる#'zが(access)ライブラリ自体の文脈情報を持つことによる。ypsilonは、exportに含まれていない変数はライブラリ文脈に含まれないため、(foo login-name)はunbound variableとなる。
常識的なシチュエーションで使われるdatum→syntaxはreflection safeであることが多い。わざわざこのような衝突を行わせることは稀だからだ。ただ、reflection safetyを強制するかどうかは絶妙な問題だろう。
ypsilonのようにexportに含まれるものだけをライブラリ文脈に含めるのは折衷案としては良いかもしれないが、事実上の参照実装であるところのchez scheme(や、psyntax)と互換性が無いのが微妙とも言える。