From f35fd45d4cbb3ac3b7c177cdbd2d4bbefd63a074 Mon Sep 17 00:00:00 2001 From: "michele.simionato" Date: Sat, 20 Jun 2009 13:43:55 +0000 Subject: Published scheme29 --- artima/scheme/Makefile | 4 +- artima/scheme/scheme22.ss | 3 +- artima/scheme/scheme28.ss | 416 ++++++++++++++++++++++------------------------ artima/scheme/scheme29.ss | 220 +++++++++++++++++++++++- 4 files changed, 419 insertions(+), 224 deletions(-) (limited to 'artima') diff --git a/artima/scheme/Makefile b/artima/scheme/Makefile index 3ca9ca6..6f41a13 100644 --- a/artima/scheme/Makefile +++ b/artima/scheme/Makefile @@ -74,8 +74,8 @@ linkcheck: @echo "Link check complete; look for any errors in the above output " \ "or in .build/linkcheck/output.txt." -POST = python2.5 ../post.py -RST = python2.5 ../../scheme2rst.py -r +POST = python ../post.py +RST = python ../../scheme2rst.py -r 1: scheme1.ss $(RST) scheme1.ss; $(POST) scheme1.rst 238789 diff --git a/artima/scheme/scheme22.ss b/artima/scheme/scheme22.ss index a52f387..0c6e0b2 100644 --- a/artima/scheme/scheme22.ss +++ b/artima/scheme/scheme22.ss @@ -217,7 +217,7 @@ the whole of R6RS. You could even perform the opposite, and remove ``set!`` from the run-time, but allowing it at compile time. However, personally I do not feel a need to distinguish the languages -at different phases and I like Scheme to be a Lisp-1 language with a +at different phases and I like Scheme to be a Lisp-1_ language with a single namespace for all variables. I am also not happy with having to keep manually track of the meta-levels, which is difficult and error prone when writing higher order macros. Moreover, in PLT and @@ -238,5 +238,6 @@ the programmer does not need to think about it explicitly). The model of implicit phasing was proposed by Kent Dybvig and Abdul Aziz Ghuloum, who wrote his `Ph. D. thesis`_ on the subject. +.. _Lisp-1: http://en.wikipedia.org/wiki/Lisp-1#The_function_namespace .. _Ph. D. thesis: http://portal.acm.org/citation.cfm?id=1291151.1291197&coll=GUIDE&dl=GUIDE&CFID=34012650&CFTOKEN=38507862 |# diff --git a/artima/scheme/scheme28.ss b/artima/scheme/scheme28.ss index 32ec794..af25a29 100644 --- a/artima/scheme/scheme28.ss +++ b/artima/scheme/scheme28.ss @@ -1,26 +1,38 @@ #|Hygienic macros ============================================================== -In episode 9_ I akwnoledged the fact that Scheme provides at least three -different macro systems - ``syntax-rules``, ``syntax-case`` and -``define-macro`` - yet I have -decided to explain only my own personal macro system - ``sweet-macros``. -The decision was motivated by pedagogical reasons, because I did not want -to confuse my readers by describe too many macro frameworks -at the same time, and also because I want to make macros -easier, by providing a nicer syntax and introspection features. -However now, after more than a dozen -episodes about macros, I can assume my readers are beginners no more, -and it is time to have a look at the larger Scheme world and to -compare/contrast ``sweet-macro`` with the most used systems, i.e. -``syntax-rules``, ``syntax-case`` and ``define-macro``. - -Actually, there a few other intesting options -out there, such as syntactic closures and systems based on explicit -renaming. Since I do not want to discuss all the macro systems in existence -here, I will point out a good resource on the -subject: this excellent `post by Alex Shinn`_ on the Chicken mailing list -summarizes the situation better than I could do. +In episode 9_ I noted that Scheme provides three +major macro systems (``syntax-rules``, ``syntax-case`` and +``define-macro``), yet I went on to discuss +my own personal macro system, ``sweet-macros``. +The decision was motivated by various reasons. +First of all, I did not want +to confuse my readers by describing too many macro systems +at the same time. Secondly, I wanted to make macros +easier and more debuggable. Finally, ``sweet-macros`` are +slightly more powerful than the other macro systems, with a better +support for guarded patterns and with a few extensibility features +which I have not shown yet (but I like to keep some trick under my +sleeve ;). + +After 19 episodes about macros, I can safely assume that my +readers are not beginners anymore. It is time to have a look at the +larger Scheme world and to compare/contrast ``sweet-macros`` with the +other macro systems. I do not want to discuss all the macro +systems in existence here, therefore I will skip a few interesting systems +such as syntactic closures and explicit renaming macros. However, +readers interested in alternative macro systems for Scheme should have +a look at this excellent `post by Alex Shinn`_ which summarizes the +current situation better than I could do. Notice that Alex is +strongly biased against ``syntax-case`` and very much in favor of +explicit renaming macros. The two +systems are not incompatible though, and actually Larceny provides a +``syntax-case`` implementation built on top of explicit renaming +macros (see also SRFI-72_). + +.. _SRFI-72: http://srfi.schemers.org/srfi-72/srfi-72.html +.. _The Scheme Programming Language: http://www.scheme.com/tspl3/ +.. _Syntax-Rules Primer for the Mildly Insane: http://groups.google.com/group/comp.lang.scheme/browse_frm/thread/86c338837de3a020/eb6cc6e11775b619?#eb6cc6e11775b619 ``syntax-match`` vs ``syntax-rules`` ----------------------------------------------------------------- @@ -31,19 +43,22 @@ summarizes the situation better than I could do. (def-syntax (syntax-rules (literal ...) (patt templ) ...) #'(syntax-match (literal ...) (sub patt #'templ) ...)) -As you see, the difference between ``syntax-rules`` (a part for -missing the ``sub`` literal) is that ``syntax-rules`` automatically +As you see, the main difference between ``syntax-rules`` (apart for a +missing ``sub``) is that ``syntax-rules`` automatically adds the syntax-quote ``#'`` operator to you templates. That means that you cannot use quasisyntax tricks and that ``syntax-rules`` is -strictly less powerful than ``syntax-match``. Moreover, -``syntax-rules`` macros do not have guarded patterns; the most direct -consequence is that providing good error messages for wrong syntaxes -is more difficult. +strictly less powerful than ``syntax-match``. The other difference is +that ``syntax-rules`` macros do not have guarded patterns; the most direct +consequence is that providing good error messages for incorrect syntax +is more difficult. You may learn everything you ever wanted to know +about ``syntax-rules`` in the `Syntax-Rules Primer for the Mildly Insane`_ +by Joe Marshall. + ``syntax-match`` vs ``syntax-case`` ----------------------------------------------------------------- -``syntax-case`` could be defined in terms of ``syntax-match`` as +In principle, ``syntax-case`` could be defined in terms of ``syntax-match`` as follows:: (def-syntax syntax-case @@ -57,17 +72,35 @@ follows:: In reality, ``syntax-case`` is a Scheme primitive and ``syntax-match`` is defined on top of it. So, ``syntax-case`` has theoretically the same power as ``syntax-match``, but in practice -``syntax-match`` is more convenient to use because of the -introspection features. +``syntax-match`` is more convenient to use. -The major syntactic difference (apart from the absense of the ``sub`` literal) +The major syntactic difference is the position of the guard, which in ``syntax-case`` is positioned *before* -the skeleton, whereas in ``syntax-match`` is positioned *after* the skeleton. -Changing the position has cost me a lot of reflection, since I *hate* -gratuitous breaking. However, I am convinced that the position of the +the skeleton, whereas in ``syntax-match`` is positioned *after* the skeleton +I did spent a lot of time thinking about the right position for the guard: +I hate gratuitous breaking, but I convinced myself that the position of the guard in ``syntax-case`` is really broken, so I had to *fix* the issue. +The problem with ``syntax-case`` is that while the pattern is always +in the first position, you never know what is in the +second position: it could be the guard *or* the template; in order +to distinguish the possibilities you have to check if there is +a third expression in the clause and that is annoying, + +In ``syntax-match`` the template +is *always* in the second position; if there is something in the +third position, it is always a guard; moreover, there could be +another expression in the clause (in the fourth position) which is +used as alternative template if the guard is not satisfied. +The advantage of having fixed positions is that it is easier to +write higher order macros expanding to ``syntax-match`` transformers +(``syntax-match`` itself is implemented in terms of ``syntax-case`` +where the template are not in fixed position and I had to make the +implementation more complex just to cope with that). + +You may learn (nearly) everything there is to know +about ``syntax-case`` in the the book +`The Scheme Programming Language`_ by Kent Dybvig. -XXX: why do I say so? ``syntax-match`` versus ``define-macro`` --------------------------------------------------------------- @@ -75,7 +108,7 @@ XXX: why do I say so? Nowadays macros based on ``define-macro`` are much less used than in past because macro systems based on pattern matching are much more powerful, easier and safer to use. The R6RS specification made -``syntax-case`` enter in the standard and this is the preferred macro +``syntax-case`` enter in the standard and it is the preferred macro system for most implementation. Nowadays, there is a good chance that your Scheme implementation does @@ -87,8 +120,16 @@ $$DEFINE-MACRO The code should be clear: the arguments of the macro are converted into a regular list which is then transformed with the expander, and converted -back into a syntax object in the context of the macro. +back into a syntax object in the context of the macro. +``define-macro`` macros are based on simple list manipulations and +are very easy to explain and to understand: unfortunately, they +are affected by the hygiene problem. + +You can find examples of use +of ``define-macro`` in many references; I learned it from +`Teach Yourself Scheme in Fixnum Days`_ by Dorai Sitaram. +.. _Teach Yourself Scheme in Fixnum Days: http://www.ccs.neu.edu/home/dorai/t-y-scheme/t-y-scheme-Z-H-1.html .. _9: http://www.artima.com/weblogs/viewpost.jsp?thread=240804 .. _On Lisp: http://www.paulgraham.com/onlisp.html .. _hygiene in R6RS: http://docs.plt-scheme.org/r6rs-lib-std/r6rs-lib-Z-H-13.html#node_sec_12.1 @@ -99,181 +140,137 @@ The hygiene problem If you have experience in Common Lisp or other Lisp dialects, you will have heard about the problem of hygiene in macros, a.k.a. the problem -of *variable capture*. In Scheme you have the same problem if you use -``define-macro``. The issue is that having macros -introducing identifiers implicitly can cause unespected side -effects. As Paul Graham puts it, +of *variable capture*. As Paul Graham puts it, *errors caused by variable capture are rare, but what they lack in frequency they make up in viciousness*. - -The hygiene problem is the main reason why `define-macro`` is becoming +The hygiene problem is the main reason why ``define-macro`` is becoming less and less used in the Scheme world. PLT Scheme has being -deprecating it for many years and nowadays even Chicken Scheme, which -traditionally used ``define-macro`` a lot, has removed it from the +deprecating it for years and nowadays even Chicken Scheme, which +traditionally used ``define-macro``, has removed it from the core, by using hygienic macros instead: this is the reason why the -current Chicken (Chicken 4.0) is called "hygienic Chicken". +current Chicken (Chicken 4) is called "hygienic Chicken". -You can find good discussions of the hygiene problem in Common Lisp -in many places; I am familiar with Paul Graham's book `On Lisp`_ which -I definitively recommend: the chapter on variable chapter is the best -reference I know. Another good reference is the chapter -about ``syntax-case`` - by Kent Dybvig - in the book `Beautiful Code`_. -Here I will give just a short example exhibiting the problem, for the -sake of the readers unfamiliar with it. +You can find good discussions of the hygiene problem in Common Lisp in +many places; I am familiar with Paul Graham's book `On Lisp`_ which I +definitively recommend: chapter 9 on variable capture +has influenced this section. Another good +reference is the chapter about ``syntax-case`` - by Kent Dybvig - in +the book `Beautiful Code`_. Here I will give just a short example +exhibiting the problem, for the sake of the readers unfamiliar with +it. .. image:: hygienic-paper-small.jpg -Consider for instance this "dirty" definition of the ``for`` loop: +Consider this "dirty" definition of the ``for`` loop: + +$$DIRTY-FOR + +Superficially ``define-macro`` looks quite similar to ``def-syntax``, +except that in the macro body you need to a add a comma in front of +each macro argument argument. Internally, however, macros based on +``define-macro`` are completely different. In particular, they are not +safe under variable capture and that may cause surprises. +For instance, code such as .. code-block:: scheme - (define-macro (dirty-for i i1 i2 . body) - `(let ((start ,i1) (stop ,i2)) - (let loop ((i start)) - (unless (>= i stop) ,@body (loop (+ 1 i)))))) + > (let ((start 42)) + (dirty-for i 1 3 (display start) (newline))) + 1 + 1 + +prints the number 1 (twice) and not the number 42! -The mistake here is having forgotten to ``gensym`` the newly -introduced variables ``start`` and ``stop``, a common mistake for -beginners (and occasionally, even for non beginners). That means that -the macro is not safe under variable capture and indeed code such as +The reason is clear if you expand the macro (notice +the if you implement ``define-macro`` in terms of +``syntax-match`` then ``syntax-expand`` still works): .. code-block:: scheme - > (let ((start 42)) - (dirty-for i 1 3 (print start))) - 11 - -prints twice the number 1 and not the number 42. On the other hand, -everything works fine if the ``for`` macro is defined using -``def-syntax``. There is no doubt that ``def-syntax`` is -nicer/simpler to writer than ``define-macro`` *and* much less error -prone. - -The other problem with non-hygienic macros is that introduced identifiers -will have the scope of expanded code, not the scope of the original macro: -that means that if the outer scope redefines the meaning of an -identifier used internally, the macro will work in an unexpected way. -Consider for instance the following expression: + > (syntax-expand (dirty-for i 1 3 (display start) (newline))) + (let ((start 1) (stop 3)) + (let loop ((i start)) + (unless (>= i stop) (display start) (newline) + (loop (+ 1 i))))) -> (let ((unless 'unless)) - (dirty-for i 1 3 (print i))) +Since the inner variable ``start`` is shadowing the outer variable ``start``, +the number 1 is printed instead of the number 42. +The problem can be solved by introducing unique identifiers in the +macro by means of ``gensym`` +(``gensym`` is not in the R6RS standard, but in practice every Scheme +implementation has it; for convenience I have included it in my +``(aps compat)`` compatibility library). -There is an error here because shadowing ``unless`` affects the -``dirty-for`` macro. This is pretty tricky to debug: in practice, it -means that the macro user is forced to know all the identifiers -that are used internally by the macro. +The ``dirty-for`` macro +can be improved to use ``gensym`` for every variable which is internally +defined (and it is not a macro argument): -.. _Beautiful Code: http://oreilly.com/catalog/9780596510046/ +$$FOR-WITH-GENSYM -Breaking hygiene -------------------------------------------------- +.. code-block:: scheme -If you only use hygienic macros, the hygiene probleme does not exist. -However, there is the opposite problem: you need a way of breaking -hygiene on purpose. Consider for instance the following apparently -trivial macro: + > (let ((start 42)) + (less-dirty-for i 1 3 (display start) (newline))) + 42 + 42 + +``less-dirty-for`` works because all internal variables have now +unique names that cannot collide with existing identifiers by +construction. You can see the names used internally by invoking +``syntax-expand`` (notice that by construction such names change every +time you expand the macro): .. code-block:: scheme - (def-syntax (define-a x) - #`(define a x)) - -``(define-a x)`` *apparently* should expand to ``(define a x)``, so -you may find the following surprising:: - - > (define-a 1) - > a - Unhandled exception - Condition components: - 1. &undefined - 2. &who: eval - 3. &message: "unbound variable" - 4. &irritants: (a) - -Why is the variable ``a`` not defined? The reason is that Scheme -macros are *hygienic*, i.e. they *do not introduce identifiers -implicitly*. Auxiliary names introduced in a macro *are not visible -outside*: the only names which enter in the expansion are the ones we -put in. - -This is a major difference with respect to Common Lisp macros. In -Common Lisp the mechanism of macro expansion is much simpler to -explain, since the expansion is literal: you could just cut and paste -the result of the expansion in your original code. In Scheme instead -the expansion is not literally inserted in the original code, and a -lot of magic takes place to avoid name clashes. In practice, the -implementation of Scheme macros takes care of distinguishing the -introduced identifiers with some specific mechanism (it -could be based on marking the names, or on explicit renaming). - -Once you get used to the idea that the expansion is not -literal, and that all the identifiers internally used by a macro -are opaque unless they are explicitly marked as visible, you will -see the advantages of hygiene. - -For instance, if you are writing a library which can be imported -in an unknown environment, in absence of hygiene you could introduce -name clashes impossible to foresee in advance, and that could be solved -only by the final user, which however will likely be ignorant of how -your library works. - -To be fair, I should -remark that in Common Lisp there -are ways to work around the absence of hygiene; -nevertheless I like the Scheme way -better, since by default you cannot introduce unwanted names. If -want to introduce new names you can, but you must say so. Introducing -new names in a macro is called *breaking hygiene*. - -Scheme provides a builtin mechanism to break hygiene, via the -``datum->syntax`` utility which converts -literal objects (*datums*) into syntax objects. - -I have already shown ``datum->syntax`` -at work in the definition of ``define-macro`` from ``syntax-match``, -where it was used to convert a list describing source code into a syntax -object. - -A typical use case for ``datum->syntax`` is to turn symbols -into proper identifiers which can be introduced in macros and made -visible to expanded code, thus breaking hygiene. Here is how -you can "fix" the macro ``define-a``: - -$$DEFINE-A - -Notice that I have used the name of the macro as the context identifier -in ``datum->syntax``. This is the common practice, but any dummy -identifier would have worked for this example. You can check that -the identifier ``a`` is really introduced as follows: + > (syntax-expand (less-dirty-for i 1 3 (display i))); in Ikarus + (let ((#{g0 |K!ZoUGmIIl%SrMfI|} 1) (#{g1 |qecoGeEAOv0R8$%0|} 3)) + (let #{g2 |kD?xfov61j0$G1=M|} ((i #{g0 |K!ZoUGmIIl%SrMfI|})) + (unless (>= i #{g1 |qecoGeEAOv0R8$%0|}) (display i) + (#{g2 |kD?xfov61j0$G1=M|} (+ 1 i))))) + +Unfortunately ``less-dirty-for`` is not really clean. +``gensym`` cannot do anything to solve the *free-symbol capture* +problem (I am using Paul Graham's terminology here). + +The problem is that identifiers used (but not defined) in the macro +have the scope of expanded code, not the scope of the original macro: +in particular, if the outer scope in expanded code redefines the meaning of an +identifier used internally, the macro will not work as you would expect. +Consider for instance the following expression: .. code-block:: scheme - > (define-a 1) - > a - 1 + > (let ((unless 'unless)) + (less-dirty-for i 1 3 (display i))) -A more realistic example is to build identifiers from strings and -symbols. For that purpose I have added an ``identifier-append`` -utility in my ``(aps lang)`` library, defined as follow: +If you try to run this, your system will go into an infinite loop! -$$lang:IDENTIFIER-APPEND +The problem here is that shadowing ``unless`` in the outer scope +affects the inner working of the macro (I leave as an exercise to +understand what exactly is going on). +As you can easily see, this +kind of problem is pretty tricky to debug: in practice, it +means that the macro user is forced to know all the identifiers +that are used internally by the macro. -Here is a simple ``def-book`` macro using ``identifier-append``: +On the other hand, +if you use hygienic macros, all the subtle +problems I described before simply disappear and +you can write a clean for loop just as it should be written: -$$DEF-BOOK +$$CLEAN-FOR -to be used as in the following test: +Everything works fine with this definition, and the macro looks +even better, with less commas and splices ;-) -$$TEST-DEF-BOOK +That's all for today. The next episode will discuss how to break +hygiene on purpose, don't miss it! + +.. _Beautiful Code: http://oreilly.com/catalog/9780596510046/ -There are better ways to define records in Scheme, and there is also -a built-in facility to define record types: you should consider -``def-book`` just as an example of use of ``identifier-append``, -not as a recommended pattern to define records. |# -(import (rnrs) (sweet-macros) (aps lang) (aps list-utils) (aps compat) - (aps test-utils)) +(import (rnrs) (sweet-macros) (aps lang) (aps list-utils) (aps compat)) ;DEFINE-MACRO (def-syntax define-macro @@ -286,51 +283,30 @@ not as a recommended pattern to define records. )) ;END -;;DEFINE-A -(def-syntax (define-a x) - #`(define #,(datum->syntax #'define-a 'a) x)) -;;END +;DIRTY-FOR + (define-macro (dirty-for i i1 i2 . body) + `(let ((start ,i1) (stop ,i2)) + (let loop ((,i start)) + (unless (>= ,i stop) ,@body (loop (+ 1 ,i)))))) +;END + +;FOR-WITH-GENSYM + (define-macro (less-dirty-for i i1 i2 . body) + (let ((start (gensym)) (stop (gensym)) (loop (gensym))) + `(let ((,start ,i1) (,stop ,i2)) + (let ,loop ((,i ,start)) + (unless (>= ,i ,stop) ,@body (,loop (+ 1 ,i))))))) +;END + +;CLEAN-FOR + (def-syntax (clean-for i i1 i2 body1 body2 ...) + #'(let ((start i1) (stop i2)) + (let loop ((i start)) + (unless (>= i stop) body1 body2 ... (loop (+ 1 i)))))) +;END -;DEFINE-A-NH -(define-macro (define-a* x) +;DEFINE-A +(define-macro (define-a x) `(define a ,x)) ;END -;;DEF-BOOK -(def-syntax (def-book name title author) - (: with-syntax - name-title (identifier-append #'name "-title") - name-author (identifier-append #'name "-author") - #'(begin - (define inner-name (vector title author)) - (define name-title (vector-ref inner-name 0)) - (define name-author (vector-ref inner-name 1))))) - -;;END - -;(pretty-print (syntax-expand (def-book bible "The Bible" "God"))) - - ;;TEST-DEF-BOOK - (test "def-book" - (let () - (def-book bible "The Bible" "God") - (list bible-title bible-author)) - (list "The Bible" "God")) - ;;END - -(def-syntax (let-3 name list-3 body body* ...) - #`(let+ ((x y z) list-3) - (let ((#,(identifier-append #'name ".x") x) - (#,(identifier-append #'name ".y") y) - (#,(identifier-append #'name ".z") z)) - body body* ...))) - -(pretty-print (syntax-expand - (let-3 v '(a b c) - (display (list v.x v.y v.z))) -)) - - -(let-3 v '(a b c) - (display (list v.x v.y v.z))) -(newline) diff --git a/artima/scheme/scheme29.ss b/artima/scheme/scheme29.ss index 1245246..af83c0a 100644 --- a/artima/scheme/scheme29.ss +++ b/artima/scheme/scheme29.ss @@ -1,6 +1,160 @@ #|Comparing identifiers ============================================================== + +Hygienic vs non-hygienic macro systems +---------------------------------------------- + +Understanding the hygiene issue is important if you intend to work +in the larger Lisp world. In the small Scheme world +everybody thinks that hygiene is an essential feature and nowadays +all major Scheme implementations provide hygienic macros; +however, in the rest of the world things are different. + +Common Lisp does not use hygienic macros and cope with the +variable capture problem by using ``gensym``; the free symbol +capture problem is not solved, but it is rare. Also the fact +that Common Lisp has multiple namespaces and a package system +helps to mitigate the issue. + +The hygiene problem is more +serious in Lisp-1_ dialects like the newborn Arc_ and Clojure_: +still their authors, which are extremely competent programmers, +have decided not to use hygienic macros and to rely on various workarounds. + +Personally I have made my mind up and I am in the pro-hygiene camp now. +I should admit that for a long time I have been in the opposite +camp, preferring the simplicity of ``define-macro`` over +the complexity of ``syntax-case``. It turns out I was wrong. +The only problem of ``syntax-case`` is a cosmetic one: it +looks very complex and cumbersome to use, but that can be easily +solved by providing a nicer API - which I did with ``sweeet-macros``. +I believe that +eventually all Lisp dialects will start using hygienic macros, but +that could take decades, because of inertia and backward-compatibility +concerns. + +Having attended to a talk on the subject at the `EuroLisp +Symposium`_, I will mention here that Pascal Costanza has found a way to +implement hygienic macros on top of ``defmacro`` in Common +Lisp *portably*. There is no technical reason why +hygienic macros are not widespread in the whole Lisp world, +just a matter of different opinions on the importance of +the problem and the different tradeoffs. + + +.. _Arc: http://arclanguage.org/ +.. _Clojure: http://clojure.org/ +.. _Lisp-1: http://en.wikipedia.org/wiki/Lisp-1#The_function_namespace +.. _EuroLisp Symposium: http://www.european-lisp-symposium.org/ + +The hygiene problem (II) +------------------------------------------------- + +However, there is the opposite problem: you need a way of breaking +hygiene on purpose. Consider for instance the following apparently +trivial macro: + +$$DEFINE-A + +``(define-a x)`` *apparently* should expand to ``(define a x)``, so +you may find the following surprising:: + + > (define-a 1) + > a + Unhandled exception + Condition components: + 1. &undefined + 2. &who: eval + 3. &message: "unbound variable" + 4. &irritants: (a) + +Why is the variable ``a`` not defined? The reason is that Scheme +macros are *hygienic*, i.e. they *do not introduce identifiers +implicitly*. Auxiliary names introduced in a macro *are not visible +outside*: the only names which enter in the expansion are the ones we +put in. + +This is a major difference with respect to Common Lisp macros. In +Common Lisp the mechanism of macro expansion is much simpler to +explain, since the expansion is literal: you could just cut and paste +the result of the expansion in your original code. In Scheme instead +the expansion is not literally inserted in the original code, and a +lot of magic takes place to avoid name clashes. In practice, the +implementation of Scheme macros takes care of distinguishing the +introduced identifiers with some specific mechanism (it +could be based on marking the names, or on explicit renaming). + +Once you get used to the idea that the expansion is not +literal, and that all the identifiers internally used by a macro +are opaque unless they are explicitly marked as visible, you will +see the advantages of hygiene. + +For instance, if you are writing a library which can be imported +in an unknown environment, in absence of hygiene you could introduce +name clashes impossible to foresee in advance, and that could be solved +only by the final user, which however will likely be ignorant of how +your library works. + +To be fair, I should +remark that in Common Lisp there +are ways to work around the absence of hygiene; +nevertheless I like the Scheme way +better, since by default you cannot introduce unwanted names. If +want to introduce new names you can, but you must say so. Introducing +new names in a macro is called *breaking hygiene*. Since I like +to keep you in suspense, I will close this episode here, and I will +make you wait for the next one to discover how to break hygiene ;-) + +Breaking hygiene +-------------------------------------------- + +Scheme provides a builtin mechanism to break hygiene, via the +``datum->syntax`` utility which converts +literal objects (*datums*) into syntax objects. + +I have already shown ``datum->syntax`` +at work in the definition of ``define-macro`` from ``syntax-match``, +where it was used to convert a list describing source code into a syntax +object. + +A typical use case for ``datum->syntax`` is to turn symbols +into proper identifiers which can be introduced in macros and made +visible to expanded code, thus breaking hygiene. Here is how +you can "fix" the macro ``define-a``: + +$$DEFINE-A + +Notice that I have used the name of the macro as the context identifier +in ``datum->syntax``. This is the common practice, but any dummy +identifier would have worked for this example. You can check that +the identifier ``a`` is really introduced as follows: + +.. code-block:: scheme + + > (define-a 1) + > a + 1 + +A more realistic example is to build identifiers from strings and +symbols. For that purpose I have added an ``identifier-append`` +utility in my ``(aps lang)`` library, defined as follow: + +$$lang:IDENTIFIER-APPEND + +Here is a simple ``def-book`` macro using ``identifier-append``: + +$$DEF-BOOK + +to be used as in the following test: + +$$TEST-DEF-BOOK + +There are better ways to define records in Scheme, and there is also +a built-in facility to define record types: you should consider +``def-book`` just as an example of use of ``identifier-append``, +not as a recommended pattern to define records. + What does it mean that two identifiers are equal in a lexically scoped language with hygienic macros? @@ -156,7 +310,18 @@ considered ``free-identifier=?``: > (free-identifier=? #'a #'b) #f -Moreover +Moreover you can use ``free-identifier=?`` to compare two unbound +identifiers or a bound and an unbound identifier. It returns true only +if the identifiers share the same name and the same bound/unbound state. +You can leverage on this feature to define a function which is able +to determine if an identifier is bound or unbound: + +$$lang:UNBOUND? + +Using ``unbound?`` it is easy to write code that performs a certain +action only if a given identifier is bound: + +$$WHEN-BOUND For things like cond which need to distinguish macro-specific literals from bound names (e.g.: (let ((else 1)) (cond (else ---)))), @@ -311,3 +476,56 @@ the macros using the auxiliary syntax. (display (let ((a 1)) (compare-ids a a))) + +;;WHEN-BOUND +(def-syntax (when-bound (id) e* ...) + #'#f (unbound? #'id) #'(begin e* ...)) +;;END +;;DEFINE-A +(def-syntax (define-a x) + #`(define #,(datum->syntax #'define-a 'a) x)) +;;END + +;;DEF-BOOK +(def-syntax (def-book name title author) + (: with-syntax + name-title (identifier-append #'name "-title") + name-author (identifier-append #'name "-author") + #'(begin + (define inner-name (vector title author)) + (define name-title (vector-ref inner-name 0)) + (define name-author (vector-ref inner-name 1))))) + +;;END + +;(pretty-print (syntax-expand (def-book bible "The Bible" "God"))) + + ;;TEST-DEF-BOOK + (test "def-book" + (let () + (def-book bible "The Bible" "God") + (list bible-title bible-author)) + (list "The Bible" "God")) + ;;END + +(def-syntax (is-name x) + #'(begin (display 'x) (display " is a name\n")) + (identifier? #'x) + #'(begin (display 'x) (display " is not a name\n"))) + +(def-syntax (let-3 name list-3 body body* ...) + #`(let+ ((x y z) list-3) + (let ((#,(identifier-append #'name ".x") x) + (#,(identifier-append #'name ".y") y) + (#,(identifier-append #'name ".z") z)) + body body* ...))) + +(pretty-print (syntax-expand + (let-3 v '(a b c) + (display (list v.x v.y v.z))) +)) + + +(let-3 v '(a b c) + (display (list v.x v.y v.z))) +(newline) -- cgit v1.2.1