Syntax が apply できない理由

やっと Shiro さんが言ってたことが正確に理解できたかもしれない。

自分が何回評価されたか数える symbol を作って apply するとこういうバグが見つかる:

(define ec 'ec-1)
(define ec-1 'ec-2)
(define ec-2 'ec-3)

(apply + '(ec))
=> Error: got ec-2 (正しくは "got ec")

"+" の実装が間違っている。これは本来 procedure の + を syntax として実装している弊害だと思う。


その理由を説明するために、eval の動作を詳しく考えてみる。eval が (proc . args) なるリストをもらったときの動作は

  1. proc を評価する。
  2. (*)args の各要素を評価する。
  3. Dispatch する、つまり args をスタックや引数レジスタに置いて、proc に制御を移す。
  4. (**)

(1, 2 は順不同)

それに対して、(apply proc args) の動作はこれらの内 dispatch のみ、ということになる (multiarg apply はとりあえず考えない)。"proc" や "args" というシンボルは apply を呼び出した側で解決され、その結果得られたリストは評価されること無く渡される。こういう、さとるさん*1に違う動作の切り分けができるのは「proc の引数を評価するのは proc 自身ではなく、あくまで proc を呼び出す側が好きなようにする」という convention あってこそ。

ところが syntax はその性質上自分で自分の引数を評価しなくてはならない。だから (*) には「proc が syntax でなければ」という但し書きが入り、(**) には 「proc が syntax なら呼び出された側で引数を評価する」という条件式が入る。(*) と (**) は不可分であり、相互に依存している。

Dispatch 部分だけブッツリ切り出して行う apply では、(*) を諦める必要がある。しかも (**) を抑止することは事実上不可能。だから「procedure apply では引数リストは評価されないけど、syntax apply では評価が発生しちゃうよ、しょうがないね」ということになってしまう。if とかを apply するならこれは許せるかもしれないけど、+ とか本来 procedure のものがこういう動作をしては困る。

結論: 四則演算とかを RawList にしようと言った私の提案は大失敗でした。

(んー、でもこれってコンパイラでもインタプリタでも成り立つな。Shiro さんが考えてたのとはちょっと違うのかも。)

で、解決策ですが、思いついてません。今のところ五十歩百歩な案がいくつかあるだけです。

FUNCTYPE_2N を復活させる(名前は FUNCTYPE_REDUCE あたりで)。
数値比較 (<, >, =, …) とかを考慮すると適当な interface を思いつかない。call/values と receiveがこの型に当てはまらない。apply を WithTailFlag 型にするなら apply も。
太田さんの言っていた「map_eval() の戻り値を vector にする」。その上で + などを EvaledVector 型にする。
これが一番希望が持てるかも。引数リストを 2 回 parse するか、realloc を繰り返す必要がでるし、後者は特に shiro さんが以前指摘されたように第一級継続との組合せがよろしくない。でも引数リストは普通そんなに長くないので問題ないかもしれない。中核が vector 機能に依存するというあまり嬉しくない coupling の悪寒。
素直に EvaledList にする。
メモリ浪費。

悩ましい…

*1:subtle