Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>Kaz explained it beautifully and extensively.</p> <p>However, if you wouldn't care much about the double-hygiene problem, you might find this one easier to understand:</p> <pre><code>(defmacro once-only ((&amp;rest symbols) &amp;body body) ;; copy-symbol may reuse the original symbol name (let ((uninterned-symbols (mapcar 'copy-symbol symbols))) ;; For the final macro expansion: ;; Evaluate the forms in the original bound symbols into fresh bindings ``(let (,,@(mapcar #'(lambda (uninterned-symbol symbol) ``(,',uninterned-symbol ,,symbol)) uninterned-symbols symbols)) ;; For the macro that is using us: ;; Bind the original symbols to the fresh symbols ,(let (,@(mapcar #'(lambda (symbol uninterned-symbol) `(,symbol ',uninterned-symbol)) symbols uninterned-symbols)) ,@body)))) </code></pre> <p>The first <code>let</code> is backquoted twice, because it'll be part of the final expansion. The purpose is to evaluate the forms in the original bound symbols into fresh bindings.</p> <p>The second <code>let</code> is backquoted once, because it'll be part of the user of <code>once-only</code>. The purpose is to rebind the original symbols to the fresh symbols, since their forms will have been evaluated and bound to them in the final expansion.</p> <p>If the rebinding of the original symbols was before the final macro expansion, the final macro expansion would refer to the uninterned symbols instead of the original forms.</p> <p>An implementation of <code>with-slots</code> that uses <code>once-only</code> is an example that requires double-hygiene:</p> <pre><code>(defmacro with-slots ((&amp;rest slots) obj &amp;body body) (once-only (obj) `(symbol-macrolet (,@(mapcar #'(lambda (slot) `(,slot (slot-value ,obj ',slot))) slots)) ,@body))) ;;; Interaction in a REPL &gt; (let ((*gensym-counter* 1) (*print-circle* t) (*print-level* 10)) (pprint (macroexpand `(with-slots (a) (make-object-1) ,(macroexpand `(with-slots (b) (make-object-2) body)))))) ;;; With the double-hygienic once-only (let ((#1=#:g2 (make-object-1))) (symbol-macrolet ((a (slot-value #1# 'a))) (let ((#2=#:g1 (make-object-2))) (symbol-macrolet ((b (slot-value #2# 'b))) body)))) ;;; With this version of once-only (let ((#1=#:obj (make-object-1))) (symbol-macrolet ((a (slot-value #1# 'a))) (let ((#1# (make-object-2))) (symbol-macrolet ((b (slot-value #1# 'b))) body)))) </code></pre> <p>The second expansion shows that the inner <code>let</code> is shadowing the binding to the variable <code>#:obj</code> of the outter <code>let</code>. Thus, accessing <code>a</code> within the inner <code>with-slots</code> would actually access the second object.</p> <p>Note that in this example, the outter macro-expansion gets a gensym named <code>g2</code> and the inner <code>g1</code>. In normal evaluation or compilation, it would be the opposite, as forms are walked from the outter to the inner.</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload