summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--artima/scheme/scarti.txt476
-rw-r--r--artima/scheme/scheme21.ss63
-rw-r--r--artima/scheme/scheme22.ss134
-rw-r--r--artima/scheme/scheme23.ss53
-rw-r--r--artima/scheme/scheme25.ss250
-rw-r--r--artima/scheme/scheme26.ss161
-rw-r--r--artima/scheme/scheme27.ss104
-rw-r--r--scheme/aps/define-ct.sls15
-rw-r--r--scheme/aps/lang.sls1
-rw-r--r--scheme/aps/list-utils.sls29
-rw-r--r--scheme/define-ct.ss22
-rw-r--r--scheme/sweet-macros/helper3.mzscheme.sls13
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