READのrecursive-pの働き (1) — #:g1

Posted 2010-11-08 13:33:00 GMT

READ系の関数にはrecursive-pというオプションがあり、READがトップレベルからの呼ばれたのか、リーダーマクロ内などから(入れ子になって)呼ばれたかを区別するためにあるようです。
自分はいまいちこのオプションを理解していないのですが、CLtL2には、quoteのリーダーマクロを例にして、

(set-macro-character #\' 
                     #'(lambda (stream char) 
                         (declare (ignore char)) 
                         (list 'quote (read stream))))
のように、recursive-pをTにしておかないと、
(cons '#3=(p q r) '(x y . #3#))
を読んだときにラベルが上手く参照できないという例が載っています。
「'」を再定義すると元の状態と区別が付かなくなるので、「保」を定義して実験してみます。
(set-macro-character #\保
                     (lambda (stream char)
                       (declare (ignore char))
                       (list 'quote (read stream))))
(progn
  保(1 2 3 4))
;=> (1 2 3 4)
とりあえず、これはOK。
(setq *print-circle* t)

;; 本物のquote (progn (cons '#3=(p q r) '(x y . #3#))) ;=> (#1=(P Q R) X Y . #1#)

(progn (cons 保#3=(p q r) 保(x y . #3#))) >>> reference to undefined label #3#

なるほど、#3#が見付からないということです。
どういう解釈かを考えてみると、
+最初の「保」を通過した時点で#3というラベルは覚えている
+2番めの「保」でラベルのことは忘れる(トップレベルで呼ばれていると思っているから)
+読み進んでいくと#3#なんて知らないラベルを発見した
ということなのかと思いました。
ということは、2番目の「保」がラベルの定義を見ることができるようにしてやれば動くのかというと、
(progn
  (cons 保#3=(p q r)
        保#3=(x y . #3#)))
;=> ((P Q R) . #1=(X Y . #1#))
動きました。
これは、まともにrecursive-pをTにした定義では、逆にラベルが多過ぎというエラーになります。
(progn
  (cons '#3=(p q r)
        '#3=(x y . #3#)))
;>>>  multiply defined label: #3=
あとは、これがどんどんネストしていったりすることを考えればrecursive-pの意義は大体把握できた気がします。

おまけ

SBCLでは、
(progn
  (cons 保#3=(p q r)
        #3=保(x y . #3#)))
;=> ((P Q R) . #1=(X Y QUOTE #1#))
のようなものもOKみたいなのですが、CLISPはエラーになります。
エラーになっていた方が上の解釈としては納得行くのですが、実装依存だったりするのでしょうか。
HyperSpecと他の処理系の動作を確認してみなくては…。

comments powered by Disqus