Let's start Scheme

2012-03-12

マクロとコンパイラと不具合と

ずいぶん昔から以下のようなマクロが動かない。
(define-syntax define-lambda
  (syntax-rules ()
    ((_ name formals body ...)
     (define name (lambda formals body ...)))))
(define-lambda test (t rest) `(t ,t))
(test 'a 'b) ;; => (t.xxxxxx a) xxxxxxは適当な16進数
なぜか?実は原因も全部分かっているのだが、直すに当たって余計な不具合を入れないためにもこれが必要だった経緯を書いておく。

そもそも、これは何か?
これはsyntax-caseのR6RSテストスイートをパスさせるために入れた苦肉の策である。
具体的には、syntax-caseを通った式はすべて識別子に変換される。その際に、識別子の同一性を保つため同一のsymbolであれば同一の識別子になるようにしている。

何故これは起きるのか?
上記のままでは拙い場合があった。問題としては一度識別子に変換されると2度目の変換ルーチン(以下renameと呼ぶ)ではそのままその識別子返してくる。その際に、ローカルに束縛された識別子が問題になった(と思う)。だが、マクロの展開器は賢くないのでコンパイラ側でローカルに束縛された識別子をシンボルに直してやる必要があった。また、その際に単に構文情報を剥ぎ取るだけではだめで、シンボルに直された識別子が一意である必要があった。
上記の例では(define-lambda ...)の中で「t」というシンボルはすべて同一の識別子に変換される。普通にquoteされるなら構文情報を剥ぎ取るだけなので問題ないが、quasiquoteまではunrename(と呼んでいる)ルーチンは見ない。そのため、quasiquote内の最初の「t」はunrenameされた状態で出力される。

どう解決するか?
解決方法は2つあると考えていて、unrenameがquasiquoteを見る。もしくは、unrenameをやめる。
前者は正直これ以上糞コードを増やしたくないので却下。後者はなぜunrenameなどと言うものが必要だったかという経緯を考えれば問題ない気がする。
つまり、renameルーチンが識別子を受け取った際に、その識別子をコピーしてやればいいはず。そうすれば、識別子が同一過ぎて困るという問題は無いはずだ。具体的にはwrap-syntaxという処理が式を識別子に変換しているのでそれをちょっといじって、コンパイラからunrename関係の処理をごっそり削ればいけるはず。

とりあえず試してみる。

No comments:

Post a Comment