diff options
-rw-r--r-- | artima/scheme/scarti.txt | 476 | ||||
-rw-r--r-- | artima/scheme/scheme21.ss | 63 | ||||
-rw-r--r-- | artima/scheme/scheme22.ss | 134 | ||||
-rw-r--r-- | artima/scheme/scheme23.ss | 53 | ||||
-rw-r--r-- | artima/scheme/scheme25.ss | 250 | ||||
-rw-r--r-- | artima/scheme/scheme26.ss | 161 | ||||
-rw-r--r-- | artima/scheme/scheme27.ss | 104 | ||||
-rw-r--r-- | scheme/aps/define-ct.sls | 15 | ||||
-rw-r--r-- | scheme/aps/lang.sls | 1 | ||||
-rw-r--r-- | scheme/aps/list-utils.sls | 29 | ||||
-rw-r--r-- | scheme/define-ct.ss | 22 | ||||
-rw-r--r-- | scheme/sweet-macros/helper3.mzscheme.sls | 13 |
12 files changed, 989 insertions, 332 deletions
diff --git a/artima/scheme/scarti.txt b/artima/scheme/scarti.txt new file mode 100644 index 0000000..06fa5e1 --- /dev/null +++ b/artima/scheme/scarti.txt @@ -0,0 +1,476 @@ +for instance, +you could develop your program by using an interpreted implementation, +with the advantage of rapid development and debugging, and later on +deploy your program by using a compiled implementation, with the +advantage of speed and deployment. + + ; draws a cake with n candles + + (define (print-cake n) + (printf " ~a \n" (make-string n #\.)) + (printf " .-~a-.\n" (make-string n #\|)) + (printf " | ~a |\n" (make-string n #\space)) + (printf "---~a---\n" (make-string n #\-))) + + +For instance an alternative version of ``multi-define`` +could be the following:: + + (def-syntax multi-def + (syntax-match (=) + (=> (ctx (name = value) ...) + #'(begin (define name value) ...)))) + +Here the identifier ``=`` is recognized as a keyword inside the +scope of the macro. + + > (multi-def (a = 1) (b = 2) (c = (+ a b))) + > (list a b c) + (1 2 3) + +Breaking hygiene +------------------------------------------ + +Sometimes you want to break hygiene. ``def-syntax`` allows you to +do it just as easily as ``define-macro``. Here is an example, +a Python-like ``while`` loop which recognizes the "keywords" +``break`` and ``continue``. + +A simple +macro to define the three components of a three-dimensional vector +``v`` as three variables ``v.x, v.y, v.z``: + +.. code-block:: scheme + + (define (make-syntax-symbol syntax-sym . strings) + ) + + (def-syntax (define-3d-vector v vec) + #`(begin + (define #,(make-syntax-symbol #'v ".x") (vector-ref v 0)) + (define #,(make-syntax-symbol #'v ".y") (vector-ref v 1)) + (define #,(make-syntax-symbol #'v ".z") (vector-ref v 2)))) + +This macro should be compared with: + +.. code-block:: scheme + + (define (make-symbol sym . strings) + ) + + (define-macro (define-3d-vector v vec) + `(begin + (define ,(make-symbol v ".x") (vector-ref v 0)) + (define ,(make-symbol v ".y") (vector-ref v 1)) + (define ,(make-symbol v ".z") (vector-ref v 2)))) + +The definition using ``def-syntax`` is a bit uglier than the one of +``define-macro``, but this is a feature, not a bug, since breaking +hygiene is a dirty thing and it is a good thing to have a dirty syntax +for it. That should prompt people to use better solutions. For +instance in this case a better solution would be to define a second +order hygienic macro like the following one: + +.. code-block:: scheme + + (def-syntax (define-3d-vector v vec) + #'(begin + (define _v v) + (def-syntax v + ((v) #'_v) + ((v x) #'(vector-ref _v 0)) + ((v y) #'(vector-ref _v 1)) + ((v z) #'(vector-ref _v 2))))) + +so you would use the syntax ``(v x), (v y), (v z)`` instead of ``v.x, v.y, v.z`` +(more parenthesis the better ;) Notice that the auxiliary variabile ``_v`` +is introduced hygienically so that it cannot be accessed directly; still, +you can get the value of the vector with the syntax ``(v)``. + +Guarded patterns +-------------------------------------------------------------------------- + +There is another major advantage of ``def-syntax`` +versus ``define-macro``: better error messages via the usage of +guarded patterns. The general version of a clause in ``def-syntax`` +is of kind ``(skeleton condition otherwise ...)`` and if a condition +is present, the pattern is matched only if the condition is satified; +if not, if there is an ``otherwise``, specification, that is executed, +else, the matcher look at the clause. For instance the macro +``define-3d-vector v vec`` could be made more robust against +errors in this way: + +.. code-block:: scheme + + (def-syntax (define-3d-vector v vec) + #'(begin + (define _v v) + (def-syntax v + ((v) _v) + ((v x) #'(vector-ref _v 0)) + ((v y) #'(vector-ref _v 1)) + ((v z) #'(vector-ref _v 2)))) + (identifier? #'v) + (syntax-violation #'v "not a valid identifier!" #'v)) + +Now you get a meaningful error message if you try something like the following: + + +.. code-block:: scheme + + > (define-3d-vector "v" (vector 1 2 3)) + + +Last but not least, ``def-syntax`` macros, being based on syntax +objects and not just S-expressions, have information about source code +location and they are able to provide more informative error messages. + + + +Macros with helper functions +---------------------------------------------------- + +``define-macro``-style macros often use helper functions as building blocks. +``syntax-rules`` is unable to do that, but ``def-syntax``, being based on +``syntax-case`` has no trouble at all. For instance, suppose you want +to define a version of ``let`` with fewer parenthesis (as done in Arc), +such that + +.. code-block:: scheme + + (my-let (x 1 y 2) (+ x y)) + +expands to + +.. code-block:: scheme + + (let ((x 1)(y 2)) (+ x y)) + +and that you already have a list-processing ``chop`` function such that + +.. code-block:: scheme + + > (chop '(x 1 y 2)) + ((x 1) (y 2)) + +You can use the ``chop`` function inside your ``def-syntax`` macro +as simply as that: + +.. code-block:: scheme + + (def-syntax (my-let (x ...) body1 body2 ...) + #`(let #,(chop #'(x ...)) body1 body2 ...)) + +Often one wants to perform general computations at compile time, in terms +of helper functions invoked by a macro. To make this task easier, +``umacros`` provides an helper function +``syntax-apply`` that takes a function and a list of syntax objects +and return a syntax-object. + +Here an example computing the factorial of ``n`` at compile time, if ``n`` +is a literal number: + +;FACT-MACRO + +In particular, ``define-style`` macros themselves are an example of +this class of macros, since they are performing list processing at +compile time in terms of an expander function. That means that +``define-macro`` can be defined in terms of ``def-syntax`` in +a few lines of code:: + +.. code-block:: scheme + + (def-syntax define-macro + (syntax-match () + (=> (define-macro (name . params) body1 body2 ...) + #'(define-macro name (lambda params body1 body2 ...))) + (=> (define-macro name expander) + #'(def-syntax (name . args) + (datum->syntax #'name (apply expander (syntax->datum #'args))))) + )) + +``syntax-match`` and second order macros +------------------------------------------------ + +Whereas *umacros* are intended to make life easy for beginner macro +programmers, they also have the ambition to make life easier for +*expert* macro programmers. To this aim, the library alots exports +some utility which is helpful when writing complex macros. The most +important of such utilities is the macro ``syntax-match``, which is +useful when you need to access directly the transformer underlying the +macro, for instance writing second order macros, i.e. macro defining +macros. Actually ``def-syntax`` is just a thing layer of sugar over +``syntax-match``, being ``(def-syntax (name . args) body ...)`` +a shortcut for +``(define-syntax name (syntax-match (literal ...) (=> (name . args) body ...) +))``. + +Here is an example of usage of ``syntax-match``. +We define a ``named-vector`` second order macro, which allows +to define macros providing a record-like syntax to vectors. + +.. code-block:: scheme + + (def-syntax (named-vector field ...) + #'(let ((i (enum-set-indexer (make-enumeration '(field ...))))) + (syntax-match (make set! fields field ...) + (=> (_ make (field-name field-value) (... ...)) + #'(vector field-value (... ...))) + (=> (_ v set! field value) + #`(vector-set! v #,(i 'field) value)) ... + (=> (_ v field) + #`(vector-ref v #,(i 'field))) ... + (=> (_ fields) + #''(field ...)) + (=> (_ v) + #'v) + ))) + +``named-vector`` expands to a macro transformer and can be used as follows: + +.. code-block:: scheme + + > (define-syntax book (named-vector title author)) + > (book fields) + (title author) + +The macro ``book`` allows to define vectors as follows: + + > (define b (book make (title "Bible") (author "God"))) + > (book b title) + "Bible" + (book b author) + "God" + > (book b set! title "The Bible") + > (book b) + #("The Bible" "God") + +``syntax-fold`` +------------------------------------------ + +Another powerful utility provided by *umacros* is ``syntax-fold``, +which is useful in the definition of complex macros, whenever you need +to convert a list of *N* expressione into a list of *N'* expressions, +with *N'* different from *N*. Consider for instance the following +problem: convert the list of *N* elements ``(a1 a2 a3 a4 ...)`` into +the list of *2N* elements ``(a1 a1 a2 a2 a3 a3 ...)``. + +With regular fold can be done as follows: + + > (define ls '(a1 a2 a3 a4 a5 a6)) + > (fold-right (lambda (x acc) (cons* x x acc)) '() ls) + (a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6) + +This is easily done with ``syntax-fold``: + + > (define-syntax double + (syntax-fold (right acc ()) + (((_ x) #'(x x))))) + > (double 1 2 3) + + + +For instance, the previous macro can be generalized to an N-dimensional +vector as follows: + +.. code-block:: scheme + + (def-syntax (define-vector v vec) + (if (identifier? v) + #'(begin + (define _v v) + (def-syntax v + (=> (v) _v) + #,(list-comp #`(=> (v i) (vector-ref _v i)) (i) (in (range N))))) + "not a valid identifier!" #'v)) + +|# + +(import (rnrs) (ikarus) (umacros3)) + +(pretty-print (def-syntax <patterns>)) + +(def-syntax (for i i1 i2 body ...) + #'(let ((start i1) (stop i2)) + (assert (<= start stop)) + (let loop ((i start)) + (unless (>= i stop) body ... (loop (+ 1 i)))))) +; (identifier? #'i)) + +(pretty-print (for <patterns>)) +(for i 0 5 (display i)) +(newline) + +(pretty-print (syntax-expand (for i 0 5 (display i)))) +;(newline)) + +;LET/CC +(def-syntax (let/cc cont body ...) + #'(call/cc (lambda (cont) body ...))) +;END + +;WHILE +(def-syntax (while condition body ...) + (with-syntax + ((break (datum->syntax #'while 'break)) + (continue (datum->syntax #'while 'continue))) + #'(let/cc break + (let loop () + (let/cc continue + (if condition + (begin body ... (loop)) + (break))) + (loop))))) +;END + +;WHILE-EXAMPLE +(define i 0) +(while (< i 10) + (set! i (+ 1 i)) + (if (= i 2) (continue)) + (display i) + (if (= i 5) (break))) +;END + +(newline) + +;FACT-MACRO +(def-syntax fact + (letrec + ((fac (lambda (n) (if (or (= n 1) (= n 0)) 1 (* n (fac (- n 1))))))) + (syntax-match () + (=> (fact n) (datum->syntax #'fact (fac (syntax->datum #'n))))))) +;END + +(define (syntax-apply ctxt func . args) + (datum->syntax ctxt (apply func (syntax->datum args)))) + +;(def-syntax (fact n) (apply-in-ctx #'fact fac #'n)) + +(display (fact 6)) + +;(define chop (syntax-match => (a b rest ...) #'((a b) (chop rest)))) + +;(define (list-head lst n) +; (if (= 0 n) +;(define (chop-helper lst n acc) +; (if (<= (length lst) n) (reverse (cons lst acc)) +; (chop-helper (list-tail lst n) (cons acc)))) + + +Implementing literals with guards +--------------------------------------------------------- + +Literal identifiers defined via ``syntax-match`` have many advantages, +including the fact that they can be introspected. However, sometimes +you may want to implement them using guards instead. This is +advantageous if you have a single pattern and you don't need +to use the full power of ``syntax-match``, or if you want to +give customized error messages in case of wrong syntaxes. + +Here is an example:: + +> (def-syntax (for3 el in lst do something ...) + #'(apply for-each (lambda el do something ...) + (transpose lst)) + (eq? (syntax->datum #'in) 'in) + (syntax-violation 'for3 "invalid literal: required 'in'" #'in)) + +The guard strips the syntax object ``#'in`` by converting it down to a +simple datum, i.e. to a quoted Scheme expression via ``syntax->datum``, +and then checks if the datum is identical to the quoted identifier +``'in``. If not, a suitable syntax error is raised:: + + > (for3 (x y) on '((a b) (x y) (1 2)) (display x) (display y)) + Unhandled exception + Condition components: + 1. &who: for3 + 2. &message: "invalid literal: required 'in'" + 3. &syntax: + form: on + subform: #f + +You may want to hide the low-level details in your guards, i.e. the +call to ``syntax->datum``; moreover, you may want to remove +the duplication in the name of the literal identifier, which is +repeated twice; finally, you may want to extend the syntax to +check for many identifiers at once. All that can be done with a +suitable macro, as the following one:: + +> (def-syntax literal? ; a macro to be used in guards + (syntax-match (syntax) ; remember: (syntax x) means #'x + (=> (literal? (syntax name) ...) + #'(and (eq? (syntax->datum #'name) 'name) ...) + (for-all identifier? #'(name ...))))) + +``literal?`` accepts patterns of the form ``(literal? #'name ...)`` +where name is a valid identifier: this is checked early on +by the guard ``(for-all identifier? #'(name ...))`` which +is true if all the objects in the syntax list +``#'(name ...)`` are valid identifiers. + +Using this macro, ``for3`` can be rewritten as + +> (def-syntax (for4 el in lst do something ...) + #'(apply for-each (lambda el do something ...) + (transpose lst)) + (literal? #'in) + (syntax-violation 'for3 "invalid literal: required 'in'" #'in)) + + +In order to give a concrete example, here is a ``for`` +loop defined via ``def-syntax``:: + + (def-syntax (for i i1 i2 body ...) + #'(let ((start i1) (stop i2)) + (assert (<= start stop)) + (let loop ((i start)) + (unless (>= i stop) body ... (loop (+ 1 i)))))) + +It is not an accident that the syntax resembles the ``define-macro`` syntax: + +.. code-block:: scheme + + (define-macro (for i i1 i2 . body) + (let ((start (gensym)) (stop (gensym))) + `(let ((,start ,i1) (,stop ,i2)) + (let loop ((,i ,start)) + (unless (>= ,i ,stop) ,@body (loop (+ 1 ,i))))))) + +On the aestetic side, ``def-syntax`` looks more elegant than ``define-macro`` +because you can avoid all the funny commas and @-signs, as well as the gensyms +(in this example introducing the names ``start`` and ``stop`` is necessary in +order to prevent multiple evaluation, and using ``gensym`` is necessary +in order to prevent unwanted variable capture). Moreover, ``def-syntax`` +is more powerful, since it can accepts guarded patterns. For instance, +suppose we want to extend the previous ``for`` macro, by checking that +``i`` is a valid identifier. +That is easily done by using the extended form +of ``def-syntax``: + + +.. code-block:: scheme + + (def-syntax (for i i1 i2 body ...) + #'(let ((start i1) (stop i2)) + (let loop ((i start)) + (unless (>= i stop) body ... (loop (+ 1 i))))) + (identifier? #'i) + ) + +It is possible to improve the error message by adding a clause +to the guard: + +.. code-block:: scheme + + (def-syntax (for i i1 i2 body ...) + #'(let ((start i1) (stop i2)) + (let loop ((i start)) + (unless (>= i stop) body ... (loop (+ 1 i))))) + (identifier? #'i) + (syntax-violation 'def-syntax "Not a valid identifier" #'i) + ) + +The extended for ``def-syntax`` is +``(def-syntax (name . args) body fender else ...)`` +where the fender and/or the else clause are optional. diff --git a/artima/scheme/scheme21.ss b/artima/scheme/scheme21.ss index 76c51b0..1cefa4f 100644 --- a/artima/scheme/scheme21.ss +++ b/artima/scheme/scheme21.ss @@ -181,40 +181,28 @@ 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. This may be surprising at first; -for instance consider the following simple ``def-book`` macro: +takes place to avoid name clashes. -$$DEF-BOOK +This may look surprising at first; +consider for instance the following simple macro: -if we now define a book as follows +$$DEFINE-A -.. code-block:: scheme - - > (def-book bible "The Bible" "God") - -from the expansion - -.. code-block:: scheme - - > (syntax-expand (def-book bible "The Bible" "God")) - (begin - (define bible (vector "The Bible" "God")) - (define book-title (vector-ref bible 0)) - (define book-author (vector-ref bible 1))) - -one would expect the name ``book-title`` and ``book-author`` -to be defined, but this is not the case: - -.. code-block:: scheme +``(define-a x)`` expands to ``(define a x)``, so you may find the following +surprising:: - > book-title - Unhandled exception: + > (define-a 1) + > a + Unhandled exception Condition components: - 1. &who: book-title - 2. &message: "unbound identifier" - 3. &undefined - -However, once you get used to the idea that the expansion is not + 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*. +Once you get used to the idea that the expansion is not literal, and that all the names internally defined by a macro are opaque unless they are explicitly marked as visible, you will see the advantages of hygiene. @@ -234,12 +222,12 @@ frees your from wondering about name clashes. To be fair, I should remark that in Common Lisp there -are ways to work around the absence of hygiene, for instance by -introducing names with ``gensym``; nevertheless I like the Scheme way +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* and will be discussed -in the next episodes. +in the next episode. |# (import (rnrs) (sweet-macros) (for (aps lang) run expand) @@ -304,14 +292,3 @@ in the next episodes. (display ((Book author) b)) -;;DEF-BOOK -(def-syntax (def-book name title author) - #'(begin - (define name (vector title author)) - (define book-title (vector-ref name 0)) - (define book-author (vector-ref name 1)))) -;;END -(def-book bible "The Bible" "God") -(pretty-print (syntax-expand (def-book bible "The Bible" "God"))) -;book-title -;book-author diff --git a/artima/scheme/scheme22.ss b/artima/scheme/scheme22.ss index 7cf077f..838cc4d 100644 --- a/artima/scheme/scheme22.ss +++ b/artima/scheme/scheme22.ss @@ -63,17 +63,6 @@ the sense that both corresponds to the same datum:: (syntax->datum #'(display "hello"))) #t -It is possible to promote a datum to a syntax object with the -``datum->syntax`` procedure, but in order -to do so you need to provide a lexical context, which can be specified -by using an identifier:: - - > (datum->syntax #'dummy-context '(display "hello")) - #<syntax (display "hello") - -(the meaning of the lexical context in ``datum->syntax`` is tricky and -I will go back to that in future episodes). - The ``(syntax )`` macro is analogous to the ``(quote )`` macro; moreover, there is a ``quasisyntax`` macro denoted with ``#``` which is analogous to the ``quasiquote`` macro (`````) and, in analogy to @@ -111,6 +100,35 @@ rely on properties of the inner representation of syntax objects: what matters is the code they correspond to, i.e. the result of ``datum->syntax``. +It is possible to promote a datum to a syntax object with the +``datum->syntax`` procedure, but in order +to do so you need to provide a lexical context, which can be specified +by using an identifier:: + + > (datum->syntax #'dummy-context '(display "hello")) + #<syntax (display "hello") + +(the meaning of the lexical context in ``datum->syntax`` is tricky and +I will go back to that in future episodes). + +The 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 define a macro introducing an identifier ``a``: + +$$DEFINE-A + +.. code-block:: scheme + +I have used the name of the macro ``define-a`` as the context identifier +in ``datum->syntax``, which 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: + + > (define-a 1) + > a + 1 + What ``syntax-match`` really is -------------------------------------------------------------- @@ -156,68 +174,43 @@ are automatically expanded inside the syntax template, without resorting to the quasisyntax notation (i.e. there is no need for ``#```, ``#,``, ``#,@``). -Example 1: breaking hygiene --------------------------------------------------------------- - -The previous paragraphs about syntax objects have been a little abstract and -probably of unclear utility (but what would you expect from -an advanced macro tutorial? ;). In this paragraph I will be more -concrete and I will provide an useful example of usage for ``datum->syntax``. - -The 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. Coming back -to the example in the latest issue, the ``def-book`` macro, -we can introduce two identifiers for the fields ``title`` and -``author`` as follows: - -$$DEF-BOOK - -where the helper function ``identifier-append`` is defined as - -$$lang:IDENTIFIER-APPEND +Macros are in one to one correspondence with list transformers; +for you convenience, it is possible to extract the associated +transformer for each macro defined via ``def-syntax``. For instance, +here is the transformer associated to the ``define-a`` macro: -All the functions used here (``string->symbol``, ``string-append``, -``symbol->string`` work in the obvious way. Notice that for convenience -I have put ``identifier-append``, together with a companion function -``identifier-prepend`` in the ``aps`` package, in the ``(aps lang)`` module. +.. code-block:: scheme -Here is a test, showing that hygiene is effectively broken and that -the identifiers ``name-title`` and ``name-author`` are really introduced -in the namespace after expansion: + > (define tr (define-a <transformer>)) + > (tr (list #'dummy #'1)) + (#<syntax define> #<syntax a> 1) -$$TEST-DEF-BOOK +Notice that the name of the macro (in this case ``define-a`` is ignored +by the transformer, i.e. it is a dummy identifier. -**Warning**: if you have a macro depending on helper functions, like -the previous one, you must put the helper functions in a separated -module if you want to ensure portability. Moreover, you must -import the helper functions with the syntax ``(for (module-name) expand)`` -meaning that the helper functions are intended to be used at expand -time, in macros. Ikarus is quite forgiving and can just use a regular -import, but PLT Scheme and Larceny will raise an error if you do not -use the ``for expand``. A full description of the module system, with -all the gory details, will require six more episodes, and will constitute -part V of these *Adventures*. - -Example 2: matching generic syntax lists +Matching generic syntax lists -------------------------------------------------------------- -In this paragraph I will show an example of ``syntax-match``, used +The previous paragraphs about syntax objects have been a little abstract and +probably of unclear utility (but what would you expect from +an advanced macro tutorial? ;). In this paragraph I will be more +concrete and I will provide an useful example of usage for +``syntax-match``, used at its fullest potential to define a macro providing a nicer syntax for association lists (an association list is just -a non-empty list of non-empty lists). The macro will accepts a variable -number of arguments; every argument will be be of the form ``(name value)`` or -just a single identifier: in this case it will be magically converted +a non-empty list of non-empty lists). The macro accepts a variable +number of arguments; every argument is of the form ``(name value)`` or +just a single identifier: in this case it is magically converted into the form ``(name value)`` where ``value`` is the value of the identifier, assuming it is bound in the current scope, otherwise -a run time error will be raised (``"unbound identifier"``). If you try to +a run time error is raised (``"unbound identifier"``). If you try to pass an argument which is not of the expected form, a compile time -syntax error will be raised. -In concrete, the macro will work as follows: +syntax error is raised. +In concrete, the macro works as follows: $$TEST-ALIST -``(alist a (b (* 2 a)))`` will raise an error ``unbound identifier a``. +``(alist a (b (* 2 a)))`` raises an error ``unbound identifier a``. Here is the implementation: $$ALIST @@ -245,19 +238,10 @@ identifier. (display (syntax-expand (alist (a 1) (b (* 2 a))))) -;;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 name (vector title author)) - (define name-title (vector-ref name 0)) - (define name-author (vector-ref name 1))))) - +;;DEFINE-A +(def-syntax (define-a x) + #`(define #,(datum->syntax #'define-a 'a) x)) ;;END -(pretty-print (syntax-expand (def-book bible "The Bible" "God"))) - (run @@ -271,14 +255,6 @@ identifier. ; (catch-error (alist2 (a 1) (2 3))) ; "invalid syntax") - - ;;TEST-DEF-BOOK - (test "def-book" - (let () - (def-book bible "The Bible" "God") - (list bible-title bible-author)) - (list "The Bible" "God")) - ;;END ) diff --git a/artima/scheme/scheme23.ss b/artima/scheme/scheme23.ss index 834e1e7..02064fb 100644 --- a/artima/scheme/scheme23.ss +++ b/artima/scheme/scheme23.ss @@ -100,35 +100,6 @@ familiar with Paul Graham's book `On Lisp`_ which I definitively recommend. In Scheme such problem usually does not exist, but it is worth to know about it, since often people wants to break hygiene on purpose. -Consider for instance the following macro: - -$$DEFINE-A - -``(define-a x)`` expands 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*. -This just another (and perhaps simpler) manifestation of the behavior -we discussed last week. - - -``define-macro`` works as you would expect: - -$$DEFINE-A-NH - - > (define-a 1) - > a - 1 The question is: why I see that this behavior is a problem? It looks like the natural things to do, isn't it? The issue is that having macros @@ -136,8 +107,30 @@ introducing identifiers implicitly can cause unespected side effects. The problem is called variable capture. As Paul Graham puts it, "viciousness". -Here is an example of subtle bug caused by variable capture: +Consider for instance this "dirty" definition of the ``for`` loop: + +.. 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)))))) + +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 + +.. 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. bound-identifier=? and free-identifier=? ======================================================================= diff --git a/artima/scheme/scheme25.ss b/artima/scheme/scheme25.ss index edc187d..4e141d1 100644 --- a/artima/scheme/scheme25.ss +++ b/artima/scheme/scheme25.ss @@ -1,4 +1,4 @@ -#|The R6RS module system +#|Phase separation ================================================ Introduction @@ -13,56 +13,208 @@ they still got it wrong! It will takes me six full episodes to explain the module system and its trickiness, especially for macro writers who want to write portable code. -Compile time module systems versus runtime module systems ------------------------------------------------------------------ - -Since the title of this series is "The Adventures of a Pythonista in -Schemeland" I have decided to begin my escursion of the R6RS module -system by contrasting it with Python module system. -Python modules are runtime objects which can be introspectedGFCsixZ95OaX -(they are basically dictionaries); Scheme modules instead are -compile time entities which are not first class objects, and cannot -be introspected. It is not difficult to implement a Python-like -module system in Scheme, by making use of hash-tables (the equivalent -of Python dictionaries): let me begin by performing this exercise, -to make clear what a runtime module system is and to contrast it -with the compile time module system than Scheme is actually using. -The trick to define a module object is to collect all the definitions -(for simplicity let me consider only ``define`` forms) into a hashtable +.. _5: http://www.artima.com/weblogs/viewpost.jsp?thread=239699 + +The phase separation concept +------------------------------------------------------------------ + +The Scheme module system is extremely complex, because of the +complications caused by macros and because of the want of +separate compilation. However, fortunately, the complication +is hidden, and the module system works well enough for many +simple cases. The proof is that we introduced the R6RS module +system in episode 5_, and for 20 episode we could go on safely +by just using the basic import/export syntax. However, once +nontrivial macros enters in the game, things start to become +interesting. + +You can see the beginning of the problem once you start using macros +which depend from auxiliary functions. For instance, suppose you want +to define an utility to generate identifiers to be inserted +unhygienically inside macros. A typical use case is the definition of +a bunch of identifiers with different suffixes. We can perform the +task with the following helper function: + +$$lang:IDENTIFIER-APPEND + +All the functions used here (``string->symbol``, ``string-append``, +``symbol->string`` work in the obvious way. +Here is a trivial example of usage of ``identifier-append`` in a +``def-book`` macro which introduces two identifiers for the fields +``title`` and ``author``: + +$$DEF-BOOK + +Here is a test, showing that hygiene is effectively broken and that +the identifiers ``name-title`` and ``name-author`` are really introduced +in the namespace after expansion: + +$$TEST-DEF-BOOK + +Everything *seems* to work, if you try this at the REPL; in some +Scheme implementation, like Ypsilon, this will also work as a +script. +However, in most implementations, if you cut and paste the previous +lines from the REPL and convert it into a script, you will run into +an error! + +The problem is due to *phase separation*, i.e. the fact that usually (except +for some REPLs and for some Scheme implementations) macro definitions +and function definitions happens at *different times*. + +Ypsilon, as most interpreted Scheme implementations, has no phase +separation: there is no big difference between macros and functions, +which are simply recognized in the order given by their position in +the source code. In our example the ``identifier-append`` function is +defined before the ``def-book`` macro, so it can be used in the right +hand side of the macro definition. + +Life is different when you have a Scheme implementation supporting phase +separation, which means most Scheme implementations. For such implementations +macro definitions are taken in consideration +*before* function definitions, independently from their relative +position in the source code. Therefore our example fails to compile +since the ``def-book`` macro makes use of the ``identifier-append`` +function which is +*not yet defined* at the time the macro is considered, i.e. at compilation +time. The only way to make available a function defined +at runtime at compilation time is to define the function in a different +module and to import it in the original module. + +Notice that for convenience I have put ``identifier-append``, together +with a companion function ``identifier-prepend`` of obvious meaning in +the ``aps`` package, in the ``(aps lang)`` module. + + +Strong phase separation +-------------------------------------------------------------- + +This is enough to solve the problem for Ikarus, which has *weak phase +separation*, but it is not enough for PLT Scheme or Larceny, which have +*strong phase separation*. + + +In PLT Scheme instead running the script raise an error:: + + $ plt-r6rs use-registry.ikarus.ss + use-registry.ikarus.ss:5:5: compile: unbound variable in module + (transformer environment) in: register + +.. image:: salvador-dali-clock.jpg + +The problem is that PLT Scheme has *strong phase separation*: by default +names defined in external modules are imported *only* at runtime. +In some sense this is absurd since +names defined in an external pre-compiled modules +are of course known at compile time +(this is why Ikarus has no trouble to import them at compile time); +nevertheless PLT Scheme and Larceny Scheme forces you to specify +at which phase the functions must be imported. If you want to import +them at expansion time (the time when macros are processed; often +incorrectly used as synonymous for compilation time) you must say so: + +``(import (for (only (aps lang) identifier-append) expand))`` + +Discussion +------------------------------------------------- + +Is phase separation a good thing? +It is clear that for the programmer's point of view, the simplest thing +is lack of phase separation. This is the semantic typically (but now +always) chosen by Scheme interpreters and REPLs: as soon as you type +it in, an helper function is available for use in macros. +If you look at it with honesty, at the end phase separation is +nothing else that a *performance hack*: by separing compilation time +from runtime you can perform some computation at compilation time only +and gain performance. + +Therefore, if you have a compiled version of Scheme, +it makes sense to separate compilation time from runtime, and to +expand macros *before* compiling the helper functions (in absence of +phase separation, macros are still expanded before running any runtime +code, but *after* recognizing the helper functions). +Notice that Scheme has a concept of *macro expansion time* which is +valid even for interpreted implementation when there is no compilation +time. The `expansion process`_ of Scheme source code is specified in +the R6RS. + +There is still the question if strong phase separation is a good thing, +or if weak phase separation (as in Ikarus) is enough. For the programmer +weak phase separation is easier, since he does not need to specify +the phase in which he want to import names. Strong phase separation +has been introduced so that at compile time a language which is +completely different from the language you use at runtime. In particular +you could decided to use in macros a subset of the full R6RS language. + +Suppose for instance you are a teacher, and you want to force your +students to write their macros using only a functional subset of Scheme. +You could then import at compile time all R6RS procedures except the +nonfunctional ones (like ``set!``) while keeping import at runtime +the whole R6RS. You could even perform the opposite, and remove ``set!`` +from the runtime, but allowing it at compile time. + +Therefore strong phase separation is strictly more powerful than week +phase separation, since it gives you more control. In Ikarus, when +you import a name in your module, the name is imported in all phases, +and there is nothing you can do about it. +On the other hand strong phase separation makes everything more complicated: +it is somewhat akin to the introduction of multiple namespace, because +the same name can be imported in a given phase and not in another, +and that can lead to confusion. + +There are people in the Scheme community thinking that strong phase +separation is a mistake, and that weak phase separation is the right thing +to do. On the other side people (especially from the PLT community where +all this originated) sing the virtues of strong phase separation and say +all good things about it. I personally I have not seen a compelling +use case for strong phase separation yet, and I would be happy is +some of my readers could give me such an example. +On the other hand, I am well known for preferring simplicity over +(unneeded) power. + +.. _expansion process: http://www.r6rs.org/final/html/r6rs/r6rs-Z-H-13.html#node_chap_10 |# (import (rnrs) (sweet-macros) (for (aps lang) expand) (aps compat)) - -(define sentinel (gensym)) - -(def-syntax (hash-lambda h) - (syntax-match () - (sub ()))) - - -(define (alist->hash a) - (define h (make-eq-hashtable)) - (for-each (lambda (x) (hashtable-set! h (car x) (cadr x))) a) - (case-lambda - (() h) - ((name) (hashtable-ref h name sentinel)))) - -(def-syntax (module-object def ...) - (: with-syntax (name ...) (map get-name-from-define #'(def ...)) - #'(let () - def ... - (alist->hash (list (list 'name name) ...))))) - -(display (syntax-expand (module-object - (define a 1) - (define (f) a)))) - -(define mod1 - (module-object - (define a 1) - (define (f) a))) - -(display (mod1 'a)) -(display ((mod1 'f))) + +;;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 name (vector title author)) + (define name-title (vector-ref name 0)) + (define name-author (vector-ref 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 + + +;;ALIST2 +(def-syntax (alist2 arg ...) + (: with-syntax ((name value) ...) (normalize #'(arg ...)) + (if (for-all identifier? #'(name ...)) + #'(let* ((name value) ...) + (list (list 'name name) ...)) + (syntax-violation 'alist "Found non identifier" #'(name ...) + (remp identifier? #'(name ...)))))) +;;END + +(run + (let ((a 1)) + (test "mixed" + (alist2 a (b (* 2 a))) + '((a 1) (b 2)))) + ) diff --git a/artima/scheme/scheme26.ss b/artima/scheme/scheme26.ss index 6d9ee27..c961633 100644 --- a/artima/scheme/scheme26.ss +++ b/artima/scheme/scheme26.ss @@ -1,2 +1,163 @@ #| +=================================================================== + +Working around phase separation +-------------------------------------------------------------- + +I have always hated being force to put my helper functions in an +auxiliary module, because I often use auxiliary functions which +are intended to be used only once inside a given macro, thus +it makes sense to put those auxiliary functions *in the same +module* as the macro the are used in. +In principle you could solve the problem by definining all the +functions *inside* the macro, but I hate this, both for dogmatic +reasons (it is a Pythonista dogma that *flat is better than nested*) +and for pragmatic reasons, i.e. I want to be able to debug my +helper functions and this is impossible if they are hidden inside +the macro. They must be available at the top-level. Moreover, you +never know, and a functionality which was intended for use in a specific +macro my turn useful for another macro after all, and it is much +more convenient if the functionality is already encapsulated in +a nice exportable top level function. + +I am not the only to believe that it should be possible to define +helper functions in the same module as the macro and actually +many Scheme implementations provide a way to do so via a +``define-for-syntax`` form which allows to define function +at *expand time*, so that they are available for usage in macros. + +If your Scheme does not provide ``define-for-syntax``, which is not +part of the R6RS specification, you can still work around phase +separation with some clever hack. For instance, you could +use the following macro: + +$$DEFINE-CT + +The problem with auxiliary macros +------------------------------------------------------------------ + +The most common manifestation of phase separation is the problem +with auxiliary functions we have just discussed. In general however, +there is the same problem for any identifier which is used in the +right hand side of a macro definition. There is however a subtility +with auxiliary macros. + +In systems with strong phase separation, like +PLT Scheme and Larceny, auxiliary macros +are not special, and they behave as auxiliary functions: you +must put them into a separare module and you must import them +with ``(for (only (module) helper-macro) expand)`` before using them. +In system with weak phase separation, like Ikarus, or without +phase separation, like Ypsilon, *there is no need to put auxiliary +macros in an external module.* The reason is that all macro +definitions are read at the same time, and the compiler knows +about the helper macros, so it can use them. Systems with +strong phase separation are effectively using different namespaces +for each phase. + +Let me make an example. Suppose you wanted to define the macros +``define-ct`` and ``alist`` in the same module: + +.. code-block:: scheme + + (import (rnrs) (sweet-macros)) + + (def-syntax (alist arg ...) + <code here> ...) + + (def-syntax (define-ct kw (define name value) ...) + #'(def-syntax kw + (let ((a (alist (name value) ...))) + <more code here> ...))) + +In Ikarus that would be perfectly possible: the first syntax +definition would add a binding fro ``alist`` to the compile time +namespace, so that it would be seen by the second syntax definition. + +In PLT and Larceny, instead, +since the second ``def-syntax`` would still +see the standard R6RS environment - supplemented by the bindings +defined in ``sweet-macros`` - and would not see the binding for +``alist``: the net result is that you would get an error, + + +This is a precise design choice: systems with strong phase +separation are making the life harder for programmers, +by forcing them to put auxiliary macros (and functions) +in auxiliary modules, to keep absolute control on how the +names enter in the different phases and to make possible +to use different languages at different phases. + +I have yet to see a convincing example of why keeping +different languages at different phases is worth +the annoyance. + +Compile time module systems versus runtime module systems +----------------------------------------------------------------- + +Since the title of this series is "The Adventures of a Pythonista in +Schemeland" I have decided to begin my escursion of the R6RS module +system by contrasting it with Python module system. +Python modules are runtime objects which can be introspected +(they are basically dictionaries); Scheme modules instead are +compile time entities which are not first class objects, and cannot +be introspected. It is not difficult to implement a Python-like +module system in Scheme, by making use of hash-tables (the equivalent +of Python dictionaries): let me begin by performing this exercise, +to make clear what a runtime module system is and to contrast it +with the compile time module system than Scheme is actually using. +The trick to define a module object is to collect all the definitions +(for simplicity let me consider only ``define`` forms) into a hashtable + +Implementing a first class module system +----------------------------------------- + |# + +(import (rnrs) (sweet-macros) (for (aps lang) expand) (aps compat)) + +(define sentinel (gensym)) + +(def-syntax (hash-lambda h) + (syntax-match () + (sub ()))) + +;; I would write the module system over alists +(define (alist->hash a) + (define h (make-eq-hashtable)) + (for-each (lambda (x) (hashtable-set! h (car x) (cadr x))) a) + (case-lambda + (() h) + ((name) (hashtable-ref h name sentinel)))) + +(def-syntax (module-object def ...) + (: with-syntax (name ...) (map get-name-from-define #'(def ...)) + #'(let () + def ... + (alist->hash (list (list 'name name) ...))))) + +(display (syntax-expand (module-object + (define a 1) + (define (f) a)))) + +(define mod1 + (module-object + (define a 1) + (define (f) a))) + +(display (mod1 'a)) +(display ((mod1 'f))) + +;(define mod1 (alist (a 1) (f (lambda () a)))) + +(define-ct example + (define x 1) + (define y (* x 2))) + +(pretty-print (syntax-expand +(define-ct example + (define x 1) + (define y (* x 2))))) + +(display (list (example x) (example y))) + diff --git a/artima/scheme/scheme27.ss b/artima/scheme/scheme27.ss index bf24676..0994c23 100644 --- a/artima/scheme/scheme27.ss +++ b/artima/scheme/scheme27.ss @@ -14,6 +14,19 @@ that, the *same* implementation can implement phase separation *differently* in compiled code and in interpreted code, and/or differently in the REPL and in scripts! + +If you have a macro depending on helper functions, like +the previous one, you must put the helper functions in a separated +module if you want to ensure portability. Moreover, you must +import the helper functions with the syntax ``(for (module-name) expand)`` +meaning that the helper functions are intended to be used at expand +time, in macros. Ikarus is quite forgiving and can just use a regular +import, but PLT Scheme and Larceny will raise an error if you do not +use the ``for expand``. A full description of the module system, with +all the gory details, will require six more episodes, and will constitute +part V of these *Adventures*. + + Consider for instance this example in Ypsilon, which tries to implement a macro registry: @@ -44,20 +57,6 @@ simply recognized in the order given by their position in the source code. In our example the ``register`` function comes before the ``m`` macro, so it can be used in the right hand side of the macro definition. -Life is different when you have a Scheme implementation supporting phase -separation, which means most Scheme implementations. For such implementations -macro definitions are taken in consideration -*before* function definitions, independently from their relative -position in the source code. Therefore our example fails to compile -since the ``m`` macro makes use of the ``register`` function which is -*not yet defined* at the time the macro is considered, i.e. at compilation -time. The only way to make available a function defined -at runtime at compilation time is to define the function in a different -module and to import it in the original module. -This is enough to solve the problem for Ikarus, which has *weak phase -separation*, but it is not enough for PLT Scheme or Larceny, which have -*strong phase separation*. - An example will clarify the point. Suppose we define a registry module as follows @@ -136,84 +135,7 @@ However, I do not want to complicate the explanation of phase separation now, which is already complicated as it is, so let me defer a full explanation of this point to a future episode of my *Adventures*. - -Discussion -------------------------------------------------- - -Is phase separation a good thing? -It is clear that for the programmer's point of view, the simplest thing -is lack of phase separation. This is the semantic typically (but now -always) chosen by Scheme interpreters and REPLs: as soon as you type -it in, an helper function is available for use in macros. -If you look at it with honesty, at the end phase separation is -nothing else that a *performance hack*: by separing compilation time -from runtime you can perform some computation at compilation time only -and gain performance. - -Therefore, if you have a compiled version of Scheme, -it makes sense to separate compilation time from runtime, and to -expand macros *before* compiling the helper functions (in absence of -phase separation, macros are still expanded before running any runtime -code, but *after* recognizing the helper functions). -Notice that Scheme has a concept of *macro expansion time* which is -valid even for interpreted implementation when there is no compilation -time. The `expansion process`_ of Scheme source code is specified in -the R6RS. - -There is still the question if strong phase separation is a good thing, -or if weak phase separation (as in Ikarus) is enough. For the programmer -weak phase separation is easier, since he does not need to specify -the phase in which he want to import names. Strong phase separation -has been introduced so that at compile time a language which is -completely different from the language you use at runtime. In particular -you could decided to use in macros a subset of the full R6RS language. - -Suppose for instance you are a teacher, and you want to force your -students to write their macros using only a functional subset of Scheme. -You could then import at compile time all R6RS procedures except the -nonfunctional ones (like ``set!``) while keeping import at runtime -the whole R6RS. You could even perform the opposite, and remove ``set!`` -from the runtime, but allowing it at compile time. - -Therefore strong phase separation is strictly more powerful than week -phase separation, since it gives you more control. In Ikarus, when -you import a name in your module, the name is imported in all phases, -and there is nothing you can do about it. -On the other hand strong phase separation makes everything more complicated: -it is somewhat akin to the introduction of multiple namespace, because -the same name can be imported in a given phase and not in another, -and that can lead to confusion. - -There are people in the Scheme community thinking that strong phase -separation is a mistake, and that weak phase separation is the right thing -to do. On the other side people (especially from the PLT community where -all this originated) sing the virtues of strong phase separation and say -all good things about it. I personally I have not seen a compelling -use case for strong phase separation yet, and I would be happy is -some of my readers could give me such an example. -On the other hand, I am well known for preferring simplicity over -(unneeded) power. - -.. _expansion process: http://www.r6rs.org/final/html/r6rs/r6rs-Z-H-13.html#node_chap_10 |# (import (rnrs) (sweet-macros) (aps list-utils) (aps easy-test) (aps compat) (for (aps lang) expand run)) - -;;ALIST2 -(def-syntax (alist2 arg ...) - (: with-syntax ((name value) ...) (normalize #'(arg ...)) - (if (for-all identifier? #'(name ...)) - #'(let* ((name value) ...) - (list (list 'name name) ...)) - (syntax-violation 'alist "Found non identifier" #'(name ...) - (remp identifier? #'(name ...)))))) -;;END - -(run - (let ((a 1)) - (test "mixed" - (alist2 a (b (* 2 a))) - '((a 1) (b 2)))) - ) - diff --git a/scheme/aps/define-ct.sls b/scheme/aps/define-ct.sls new file mode 100644 index 0000000..761bf93 --- /dev/null +++ b/scheme/aps/define-ct.sls @@ -0,0 +1,15 @@ +(library (aps define-ct) +(export alist define-ct) +(import (rnrs) (sweet-macros) (aps lang) (aps list-utils)) + +;;DEFINE-CT +(def-syntax (define-ct kw (define name value) ...) + #'(def-syntax kw + (let ((a (alist (name value) ...))) + (syntax-match (name ...) + (sub (kw name) (datum->syntax #'kw (car (assq 'name a)))) + ...))) + (eq? (syntax->datum #'define) 'define)) +;;END + +) diff --git a/scheme/aps/lang.sls b/scheme/aps/lang.sls index 8b23089..89f8527 100644 --- a/scheme/aps/lang.sls +++ b/scheme/aps/lang.sls @@ -48,4 +48,5 @@ (string-append prefix (symbol->string (syntax->datum id)))))) ;;END + ) diff --git a/scheme/aps/list-utils.sls b/scheme/aps/list-utils.sls index b662e73..1f564a5 100644 --- a/scheme/aps/list-utils.sls +++ b/scheme/aps/list-utils.sls @@ -1,7 +1,7 @@ #!r6rs (library (aps list-utils) (export range enumerate zip transpose distinct? let+ perm list-of-aux list-for - remove-dupl append-unique fold flatten list-of normalize) + remove-dupl append-unique fold flatten list-of normalize alist) (import (rnrs) (sweet-macros) (aps cut) (for (aps lang) expand)) ;;; macros @@ -58,6 +58,18 @@ (def-syntax (list-for decl ... expr) #'(list-of-aux expr '() decl ...)) +;;ALIST +(def-syntax (alist arg ...) + (: with-syntax + ((name value) ...) + (map (syntax-match () + (sub n #'(n n) (identifier? #'n)) + (sub (n v) #'(n v) (identifier? #'n))) + #'(arg ...)) + #'(let* ((name value) ...) + (list (list 'name name) ...)))) +;;END + ;;; utilities ;;RANGE @@ -157,18 +169,11 @@ (ls in (perm eq? (remp (cut eq? el <>) lst))))))) ;;END - - - - - - - ;;NORMALIZE (define (normalize ls) - (list-of (syntax-match a () - (sub n #'(n n) (identifier? #'n)) - (sub (n v) #'(n v) (identifier? #'n))) - (a in ls))) + (map (syntax-match () + (sub n #'(n n) (identifier? #'n)) + (sub (n v) #'(n v) (identifier? #'n))) + ls)) ;;END ) diff --git a/scheme/define-ct.ss b/scheme/define-ct.ss deleted file mode 100644 index ead89c2..0000000 --- a/scheme/define-ct.ss +++ /dev/null @@ -1,22 +0,0 @@ -;;; how to define a static table from a dynamic one - -(import (rnrs) (sweet-macros) (table) (ikarus)) - -(def-syntax define-ct - (syntax-match (define) - (sub (define-ct kw (define name value) ...) - #'(def-syntax kw - (let ((t (tbl (name value) ...))) - (syntax-match (name ...) - (sub (kw name) (datum->syntax #'kw (t 'name))) ...)))))) -(define-ct example - (define x 1) - (define y (* x 2))) - -(pretty-print (syntax-expand -(define-ct example - (define x 1) - (define y (* x 2))))) - -(display (list (example x) (example y))) - diff --git a/scheme/sweet-macros/helper3.mzscheme.sls b/scheme/sweet-macros/helper3.mzscheme.sls index 9d6108e..b1cb947 100644 --- a/scheme/sweet-macros/helper3.mzscheme.sls +++ b/scheme/sweet-macros/helper3.mzscheme.sls @@ -11,12 +11,13 @@ (sub (def-syntax name transformer) #'(define-syntax name - (syntax-match (<source> <transformer>) - (sub (name <transformer>) #'(... (... transformer))) - (sub (name <source>) #''(... (... transformer))) - (sub x (transformer #'x)))) - (identifier? #'name) - (syntax-violation 'def-syntax "Invalid name" #'name)) + (lambda (x) + (syntax-case x (<source> <transformer>) + ((name <transformer>) #'(... (... transformer))) + ((name <source>) #''(... (... transformer))) + (x (transformer #'x))))) + (identifier? #'name)) + ;(syntax-violation 'def-syntax "Invalid name" #'name)) (sub (def-syntax name (extends parent) (literal ...) clause ...) #'(def-syntax name |