2015-06-25-01-外側で定義したsymbol-macroletがdo内部で展開されない
>> Site top >> weblog >> 月別アーカイブ >> 2015年06月のlog >> 2015-06-25-01-外側で定義したsymbol-macroletがdo内部で展開されない
最終更新日付:2015/06/25 01:00:00
外側で定義したsymbol-macroletがdo内部で展開されない
2015 年 06 月 25 日
symbol-macrolet でシンボルマクロを定義して、そのシンボルを do の内部で使用したら展開されないという経験をして困っている、という話。
とりあえず御託は後回しにして、やろうとしているのは、以下のようなコトだ。
(let ((arr (make-array 100 :initial-element nil))) (with-operators (let* ((ptr1 &arr) (ptr2 (_+ ptr1 (length arr)))) (do () ((_== ptr1 ptr2) arr) (setf *ptr1 (random 100)) ++ptr1))))
上記のコードでは、with-operators というのがマクロになっていて、こいつが内部のシンボルを調べて symbol-macrolet に展開する。ここでは、 &arr と *ptr1、++ptr1 をシンボルマクロにすることになる。とりあえず、1段階展開すると以下のようになる。
(let ((arr (make-array 100 :initial-element nil))) (symbol-macrolet ((&arr (operator_& arr)) (*ptr1 (operator_* ptr1)) (++ptr1 (operator_++ ptr1))) (let* ((ptr1 &arr) (ptr2 (_+ ptr1 (length arr)))) (do () ((_== ptr1 ptr2) arr) (setf *ptr1 (random 100)) ++ptr1))))
ここまで来れば何をやっているかはわかると思う。要するに C ポインタのメタファを実験しているわけだ。operator_& とかがさらにどうなるのかについては本筋から外れるため、ここでは書かない。問題は、上記からさらにシンボルマクロを展開すると ++ptr1 だけが展開されないということだ。以下のようになる。
(let ((arr (make-array 100 :initial-element nil))) (symbol-macrolet ((&arr (operator_& arr)) (*ptr1 (operator_* ptr1)) (++ptr1 (operator_++ ptr1))) (let* ((ptr1 (operator_& arr)) (ptr2 (_+ ptr1 (length arr)))) (block nil (let () (tagbody (go #:g877) #:g876 (tagbody (let* ((#:ptr1878 ptr1) (#:new879 (random 100))) (funcall #'(setf operator_*) #:new879 #:ptr1878)) ++ptr1) nil #:g877 (if (_== ptr1 ptr2) nil (progn (go #:g876))) (return-from nil (progn arr))))))))
これはどういうことだろう。同じように do の内部にあるのに、*ptr1 は展開されているのだ。そして、この残留している ++ptr1 というシンボルはエラーにもならずに無視される。一方、下記のようにしてやるとちゃんと展開される。このあたりで混乱してきた。
(let ((arr (make-array 100 :initial-element nil))) (with-operators (do* ((ptr1 &arr ++ptr1) (ptr2 (_+ ptr1 (length arr)))) ((_== ptr1 ptr2) arr) (setf *ptr1 (random 100)))))
これは正しい挙動なのだろうか? それとも、SBCL のバグなのだろうか? あ、そうそう、使っている環境は SBCL だ。今のところ他の処理系は使用していない。
そしてこういうとき、大体は正しい挙動で自分が仕様を正確に把握していないことがほとんどだ。問題は、その正しい仕様に辿りつくのにどれくらいの時間がかかるか、ということ。まずは CLtL2 でも調べてみようか。
追記
この記事に対して、「do 内部だからタグと認識されてしまっているのでは?」という指摘を頂いた。また、「だから progn で括ってみては?」とも。試してみたところ、ビンゴ。最初のコードを以下のようにするだけでイケた。
(let ((arr (make-array 100 :initial-element nil))) (with-operators (let* ((ptr1 &arr) (ptr2 (_+ ptr1 (length arr)))) (do () ((_== ptr1 ptr2) arr) (progn (setf *ptr1 (random 100)) ++ptr1)))))
この記事について twitter で書いたからというのもあるが、この指摘はほとんど即座に頂いた。そして、自分としては完全に気付いていないポイントだったので、指摘を頂かなかったらずっと悩んでいるか、あるいは諦めてしまっただろう。改めて御礼申し上げます。
コメント
このページのタグ
Page tag : Common Lisp
Copyright(C) 2005-2021 project-enigma.
Generated by CL-PREFAB.