[Top][All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

bug#62511: [PATCH] docs: Add example clarifying how syntax-case renames

From: Elijah Harding
Subject: bug#62511: [PATCH] docs: Add example clarifying how syntax-case renames symbols
Date: Tue, 28 Mar 2023 23:12:56 +0000
User-agent: Cyrus-JMAP/3.9.0-alpha0-237-g62623e8e3f-fm-20230327.001-g62623e8e

I encountered these renamed symbols in the wild, and it took quite a
while for me to figure out what was going on. I'm glad to have learned a
lot about syntax-case in the process, all the way through to Primer for
the Mildly Insane, but I think it would spare future learners some
trouble to have an example of this behavior just so that they know
what's going on when they run into it.

I've kinda awkwardly shoved the example into the most relevant place in
the manual, but I'm not sure that I'm happy with how it all flows, as I
am kinda taking a tangent for the sake of exposing these ideas. The
segue back is especially rough as I didn't want to cut the existing
example, so there are now code blocks two serving the same purpose.
Oh and I'm new to this

* doc/ref/api-macros.texi (Why syntax-case?): Document symbol renaming more 
 doc/ref/api-macros.texi | 89 ++++++++++++++++++++++++++++++++++++++---
 1 file changed, 84 insertions(+), 5 deletions(-)

diff --git a/doc/ref/api-macros.texi b/doc/ref/api-macros.texi
index a353719cb..5777eda7b 100644
--- a/doc/ref/api-macros.texi
+++ b/doc/ref/api-macros.texi
@@ -630,6 +630,8 @@ To begin with, we should mention a solution that doesn't 
       ((_ test then else)
        #'(let ((it test))
            (if it then else))))))
+(aif 't it #f)
+;; => Unbound variable: it
 @end example
 The reason that this doesn't work is that, by default, the expander will
@@ -682,6 +684,8 @@ Here's another solution that doesn't work:
        (let ((it (datum->syntax x 'it)))
          #'(let ((it test))
              (if it then else)))))))
+(aif 't it #f)
+;; => Unbound variable: it
 @end example
 The reason that this one doesn't work is that there are really two
@@ -689,9 +693,85 @@ environments at work here -- the environment of pattern 
variables, as
 bound by @code{syntax-case}, and the environment of lexical variables,
 as bound by normal Scheme. The outer let form establishes a binding in
 the environment of lexical variables, but the inner let form is inside a
-syntax form, where only pattern variables will be substituted. Here we
-need to introduce a piece of the lexical environment into the pattern
-variable environment, and we can do so using @code{syntax-case} itself:
+syntax form, where only pattern variables will be substituted.
+We can observe this effect with a similar macro that calls @code{(define
+it ...)} by examining the resulting binding, using @code{it-1} and
+@code{it-2} to distinguish the pattern variable from the syntax that we
+intend to bind it to.
+(define-syntax set-it-to-test
+  (lambda (x)
+    (syntax-case x ()
+      ((_)
+       (let ((it-1 (datum->syntax x 'it-2)))
+         #'(define it-1 'test))))))
+;; This hasn't worked:
+(module-bound? (current-module) 'it-2)
+;; => #f
+;; And hasn't bound it-1:
+(module-bound? (current-module) 'it-1)
+;; => #f
+;; But if we search for bindings that resolves to 'test,
+;; we see something strange:
+(module-map (lambda (sym val)
+              (when (eq? (variable-ref val) 'test)
+                (display (cons sym val))))
+            (current-module))
+@print{} (it-1-ee83545680dc7ed . #<variable 7fea99f57c40 value: test>)
+@end example
+We can now see that the pattern symbol @code{it-1} has not been substituted to
+@code{it-2} by the lexical binding, because the resulting variable binding
+still carries the prefix @code{it-1}.
+However @code{it-1} has not been bound either! This is because it has been
+renamed by the implementation of @code{syntax-case}, producing the gensym
+@code{it-1-ee83545680dc7ed}. This is done to preserve referential transparency,
+distinguishing our use of symbols like @code{if}, @code{define}, and @code{it}
+in the @code{syntax} forms of the transformer's definition from their use in 
+context of the form under expansion.
+This is the same mechanism which would rename the let-bound @code{it} in our
+first example to prevent the @var{then} and @var{else} expressions from
+accessing its binding. These internally renamed symbols aren't usually visible
+in eg. macroexpansions or @code{display}'d symbols, but can sometimes be
+observed doing their behind-the-scenes work within the top-level bindings of a
+What we need to do is introduce the new syntax created by @code{datum->syntax}
+into the pattern variable environment, which we can do with @code{syntax-case}
+(define-syntax set-it-to-test
+  (lambda (x)
+    (syntax-case x ()
+      ((_)
+       (syntax-case (datum->syntax x 'it-2) ()
+         (it-1
+          #'(define it-1 'test)))))))
+=> test
+@end example
+Note that by providing the template-id @code{x} we explicitly specify
+@emph{which} binding of @code{it-2} the new @code{syntax} inside our pattern
+variable @code{it-1} refers to, namely the binding within the context of the
+form being manipulated by the transformer, and we clarify to Guile that no
+distinction should be created between these two uses of @code{it-2}. If we
+provide a template-id from another (or no) context, the implementation might
+still rename our @emph{new} syntax to something like
+@code{it-2-ee83545680dc7ed} to prevent what might appear to be unintended
+variable capture.
+Returning to our previous, pragmatic example:
 ;; works, but is obtuse
@@ -710,8 +790,7 @@ variable environment, and we can do so using 
@code{syntax-case} itself:
 @print{} 500
 @end example
-However there are easier ways to write this. @code{with-syntax} is often
+There are easier ways to write this. @code{with-syntax} is often convenient:
 @deffn {Syntax} with-syntax ((pat val) @dots{}) exp @dots{}
 Bind patterns @var{pat} from their corresponding values @var{val}, within the

reply via email to

[Prev in Thread] Current Thread [Next in Thread]