@HaruoWakakusa さんのRead処理について、3点ほど気になった点があったのですが、Lisp屋さんとして一番ディープな話を書きます。
Readマクロの本質
バッククォート
cons(make_symbol("QUASIQUOTE"), cons(expr, NIL)
コンマ
cons(make_symbol("UNQUOTE"), cons(expr, NIL))
コンマ@
cons(make_symbol("UNQUOTE-SPLICING")
ここまでは、あってます。
この後、Read処理の中で、展開しないといけません。
展開する処理が、記事に書かれていなかったので、それをやっていますか?という点が一番気になりました。
実験
* '`(a ,b ,@c)
`(A ,B ,@C)
うわ。SBCLは、まさかのダメだ、こりゃ。
Clojureでやってみる
Clojure 1.11.3
user=> '`(a ,b ,@c)
(clojure.core/seq
(clojure.core/concat
(clojure.core/list (quote user/a))
(clojure.core/list (quote user/b))
(clojure.core/list (clojure.core/seq
(clojure.core/concat
(clojure.core/list (quote clojure.core/deref))
(clojure.core/list (quote user/c)))))))
バッククオート(`)の前にクオート(')をつけると、評価が抑制されるので、Readが読んだ結果が見えます。
clojure.core/ という名前空間が、ややこしいので、省略してみます。
(seq
(concat (list (quote user/a))
(list (quote user/b))
(list (seq (concat (list (quote deref))
(list (quote user/c)))))))
こんな感じになりました。
注目すべき点は、この中に、QUASIQUOTE, UNQUOTE, UNQUOTE-SPLICINGが一切入っていないという点です。
Read処理の一番最後で、QUASIQUOTE, UNQUOTE, UNQUOTE-SPLICINGを全部変換して取り除いてしまい、appendやlistやlist*やconsなどに展開しないといけません。
決して、eval処理の中で、QUASIQUOTEというマクロを呼んで展開する訳ではないのです。
『Read処理中に全て展開してしまうから、Readマクロ』と呼ぶのです
でも、動くよという反論について(理由はうまく説明できない)
もうLispを勉強したのが数十年ほど前で、その時ほど、覚えていないので、良い例が思いつきません。
検索したら以下のサイトに使用例がありました。引用しますね。
(define-macro (bar)
(let ((x 10) (y '(1 2 3)) (z 'foo))
`(list ,x `(,',z ,,@y))))
こんな感じで、バッククオートを2重にかけます。
3重にも書けるし、4重にも書けます。
私も実は昔、「バッククオートを2重に書ける」事を知りませんでした。
当時私は、やはりevalの中で、QUASIQUOTEというマクロを呼んで展開していました。
例は忘れてしまいましたが、
(a)Read処理の中で展開してしまう方法
(b)eval処理の中でQUASIQUOTEを展開する場合
では、結果に差が出る事があるのです。
そうです、バッククオートを2重や3重に書いた場合に、そのバグが発生します。
おわり
Read処理の最後で、QUASIQUOTE類は、展開して取り除いてくださいね。
それが私が考える、正しいReadの処理方法です。
(もし間違っていたらすみません)