summaryrefslogtreecommitdiff
path: root/artima
diff options
context:
space:
mode:
authormichele.simionato <devnull@localhost>2009-06-12 15:23:11 +0000
committermichele.simionato <devnull@localhost>2009-06-12 15:23:11 +0000
commit1edb00ff4c434771ce93976a2b4497f94179503a (patch)
treed210e100740593b8694415252f17273d7c417554 /artima
parent6e250c8e4cd545d829f562242acafef06234f6b3 (diff)
downloadmicheles-1edb00ff4c434771ce93976a2b4497f94179503a.tar.gz
Lots of work on my adventures
Diffstat (limited to 'artima')
-rw-r--r--artima/notes/links-and-citations.txt9
-rw-r--r--artima/scheme/Makefile6
-rw-r--r--artima/scheme/scarti.txt8
-rw-r--r--artima/scheme/scheme20.ss12
-rw-r--r--artima/scheme/scheme27.ss123
-rw-r--r--artima/scheme/scheme28.ss61
-rw-r--r--artima/scheme/scheme29.ss477
-rw-r--r--artima/scheme/scheme30.ss264
8 files changed, 664 insertions, 296 deletions
diff --git a/artima/notes/links-and-citations.txt b/artima/notes/links-and-citations.txt
index 30385d9..d8817fd 100644
--- a/artima/notes/links-and-citations.txt
+++ b/artima/notes/links-and-citations.txt
@@ -1,3 +1,7 @@
+Those who can, do; those who can't, teach -- G. B. Shaw
+
+
+
#Why I Hate Frameworks
http://discuss.joelonsoftware.com/default.asp?joel.3.219431.12
@@ -31,3 +35,8 @@ it on your own. -- Ian Bicking http://pythonpaste.org/webob/do-it-yourself.html
defined purpose as an implementation experimental platform and as a
teaching language ... the fact that it can actually be used
productively is, in some sense, incidental. -- George Neuner on c.l.s.
+
+
+
+I think one good outcome of R6RS is that library writers form a
+bridge between implementors who would not be collaborating otherwise -- Aziz
diff --git a/artima/scheme/Makefile b/artima/scheme/Makefile
index ef54fbc..3ca9ca6 100644
--- a/artima/scheme/Makefile
+++ b/artima/scheme/Makefile
@@ -169,3 +169,9 @@ eurolisp2: eurolisp2.txt
26: scheme26.ss
$(RST) scheme26.ss; $(POST) scheme26.rst 259977
+
+27: scheme27.ss
+ $(RST) scheme27.ss; $(POST) scheme27.rst 260182
+
+28: scheme28.ss
+ $(RST) scheme28.ss; $(POST) scheme28.rst 260195
diff --git a/artima/scheme/scarti.txt b/artima/scheme/scarti.txt
index c569a07..e946906 100644
--- a/artima/scheme/scarti.txt
+++ b/artima/scheme/scarti.txt
@@ -842,14 +842,6 @@ The secret of the ellipsis
macros generating macros
----------------------------------------------------
-
-There is not upper limit to the level of sophistication you can reach
-with macros: in particular it is possible to define higher order
-macros, i.e. macros taking other macros as arguments or macros
-expanding to other macros. Higher order macros allows an extremely
-elegant programming style; on the other hand, they are exposed to the
-risk of making the code incomprehensible and very hard to debug.
-
In this paragraph I will give an example of a second order macro
expanding to a regular (first order) macro. Here it is:
diff --git a/artima/scheme/scheme20.ss b/artima/scheme/scheme20.ss
index d3b6e7c..4d1ee66 100644
--- a/artima/scheme/scheme20.ss
+++ b/artima/scheme/scheme20.ss
@@ -283,21 +283,11 @@ and thus offer full autocompletion.
(def-syntax (assert-distinct arg ...)
#'(#f)
(distinct? bound-identifier=? #'(arg ...))
- (syntax-violation 'assert-distinct "Duplicate name" #'(arg ...)))
+ (syntax-violation 'assert-distinct "Duplicated name" #'(arg ...)))
;;END
;(assert-distinct a b a)
-;;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
;;TEST-DISTINCT
diff --git a/artima/scheme/scheme27.ss b/artima/scheme/scheme27.ss
index edc0b6a..de8adbf 100644
--- a/artima/scheme/scheme27.ss
+++ b/artima/scheme/scheme27.ss
@@ -1,30 +1,55 @@
#|Syntax objects
===================================================================
-In the last dozen episodes I have defined plenty of macros, but I have
-not really explained what macros are and how they work. This episode
-will close the gap, and will explain the true meaning of macros by
-introducing the concepts of *syntax object* and of *transformer* over
-syntax objects.
-
-Syntax objects
-------------------------------------------------------------------
-
-Scheme macros are built over the concept of *syntax object*.
+Scheme macros - as standardized in the R6RS document -
+are built over the concept of *syntax object*.
The concept is peculiar to Scheme and has no counterpart in other
languages (including Common Lisp), therefore it is worth to spend some time
on it.
A *syntax-object* is a kind of enhanced *s*-espression: it contains
-the source code as a list of symbols and primitive values, plus
+the source code as a list of symbols and primitive values, but also
additional informations, such as
the name of the file containing the source code, the line numbers,
a set of marks to distinguish identifiers according to their
lexical context, and more.
-It is possible to convert a name or a literal value into a
-syntax object with the syntax quoting operation, i.e. the funny
-``#'`` symbol you have seen in all the macros I have defined until now::
+The easiest way to get a syntax object is to use the syntax quoting operation,
+i.e. the funny ``#'`` symbol you have seen in all the macros I have defined
+until now. Consider for instance the following script, which displays
+on standard out the string representation of the syntax object ``#1``:
+
+.. code-block:: scheme
+
+ $ cat x.ss
+ (import (rnrs))
+ (display #'1)
+
+If you run it under PLT Scheme you will get
+
+::
+
+ $ plt-r6rs x.ss
+ #<syntax:/home/micheles/Dropbox/gcode/artima/scheme/x.ss:2:11>
+
+i.e. the full pathname of the script and the line number/column number
+where the syntax object appears in the source code. Clearly this
+information is pretty useful for tools like IDEs and debuggers. The
+internal implementation of syntax objects is totally
+implementation-dependent, so that you will get different
+informations in different implementations. For instance Ikarus
+would give
+
+::
+
+ $ ikarus --r6rs-script x.ss
+ #<syntax 1 [char 28 of x.ss]>
+
+i.e. Ikarus syntax objects do not store line numbers, they just store
+the character position from the beginning of the file. If you are using
+the REPL you will have less information, of course, and even more
+implementation-dependency. Here are a few example of syntax objects
+obtained from syntax quoting::
> #'x ; convert a name into an identifier
#<syntax x>
@@ -63,12 +88,13 @@ that both corresponds to the same datum::
(syntax->datum #'(display "hello")))
#t
-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
-the operation ``,`` and ``,@`` on regular lists, there are two
+The ``(syntax )`` macro is analogous to the ``(quote )`` macro.
+Mreover, there is a ``quasisyntax`` macro denoted with ``#``` which
+is analogous to the ``quasiquote`` macro (`````).
+In analogy to
+the operations ``,`` and ``,@`` on regular lists, there are two
operations ``unsyntax`` ``#,`` (*sharp comma*) e ``unsyntax-splicing``
-``#,@`` (*sharp comma splice*) on lists (including improper lists) of
+``#,@`` (*sharp comma splice*) on lists and improper lists of
syntax objects.
Here is an example using sharp-comma::
@@ -83,17 +109,17 @@ and here is an example using sharp-comma-splice::
(#<syntax display>
(#<syntax list> #<syntax "michele"> #<syntax "mario">) . #<syntax ()>)
-Notice that the output is an improper list. This is somewhat consistent
-with the behavior of usual quoting: for usual quoting ``'(a b c)``
-is a shortcut for ``(cons* 'a 'b 'c '())``, which is a proper list,
-and for syntax-quoting ``#'(a b c)`` is equivalent to
-``(cons* #'a #'b #'c #'())``, which is an improper list.
-The ``cons*`` operator here is a R6RS shortcut for nested conses:
-``(cons* w x y z)`` is the same as ``(cons w (cons x (cons y z)))``.
+Notice that the output - in Ikarus - is an improper list. This is
+somewhat consistent with the behavior of usual quoting: for usual
+quoting ``'(a b c)`` is a shortcut for ``(cons* 'a 'b 'c '())``, which
+is a proper list, and for syntax-quoting ``#'(a b c)`` is equivalent
+to ``(cons* #'a #'b #'c #'())``, which is an improper list. The
+``cons*`` operator here is a R6RS shortcut for nested conses: ``(cons*
+w x y z)`` is the same as ``(cons w (cons x (cons y z)))``.
However, the result of a quasi quote interpolation is very much
*implementation-dependent*: Ikarus returns an improper list, but other
-implementations returns different results; for instance ypsilon
+implementations returns different results; for instance Ypsilon
returns a proper list of syntax objects whereas PLT Scheme returns
an atomic syntax object. The lesson is that you cannot
rely on properties of the inner representation of syntax objects:
@@ -109,7 +135,7 @@ by using an identifier::
#<syntax (display "hello")
(the meaning of the lexical context in ``datum->syntax`` is tricky and
-I will go back to that in the next episode).
+I will go back to that in a future episode, when I will talk about hygiene).
What ``syntax-match`` really is
--------------------------------------------------------------
@@ -117,7 +143,7 @@ What ``syntax-match`` really is
``syntax-match`` is a general utility to perform pattern matching
on syntax objects; it takes a syntax object in output and returns
another syntax object in output, depending on the patterns, skeletons and guards
-used::
+used. Here is an example of a simple transformer based on ``syntax-match``::
> (define transformer
(syntax-match ()
@@ -128,9 +154,9 @@ used::
For convenience, ``syntax-match`` also accepts a second syntax
``(syntax-match x (lit ...) clause ...)`` to match syntax expressions
-directly, more convenient than using
+directly. This is more convenient than writing
``((syntax-match (lit ...) clause ...) x)``.
-Here is a simple example of usage::
+Here is a simple example::
> (syntax-match #'(a 1 2 3) ()
(sub (name . args) #'args)); return the args as a syntax object
@@ -142,29 +168,25 @@ Here is an example using ``quasisyntax`` and ``unsyntax-splicing``::
(sub (name . args) #`(name #,@#'args)))
(#<syntax a> #<syntax 1> #<syntax 2> #<syntax 3>)
+.. image:: hieroglyphics.jpg
+
As you see, it easy to write hieroglyphs if you use ``quasisyntax``
and ``unsyntax-splicing``. You can avoid that by means of the ``with-syntax``
form::
> (syntax-match #'(a 1 2 3) ()
- (sub (name . args) (: with-syntax (a ...) #'args #'(name a ...))))
+ (sub (name . args) (with-syntax (((a ...) #'args)) #'(name a ...))))
(#<syntax a> #<syntax 1> #<syntax 2> #<syntax 3>)
-
The pattern variables introduced by ``with-syntax``
are automatically expanded inside the syntax template, without
resorting to the quasisyntax notation (i.e. there is no need for
``#``` ``#,`` ``#,@``).
-.. image:: hieroglyphics.jpg
-
-Matching generic syntax lists
---------------------------------------------------------------
-
The previous paragraphs about syntax objects were a little abstract and
probably of unclear utility (but what would you expect from
-an advanced macro tutorial? ;). Here I will be more
-concrete and I will provide an example where
+an advanced macro tutorial? ;). Now let me be more
+concrete. I will provide an example where
``syntax-match`` is used as a list matcher inside a bigger macro.
The final goal is to provide
a nicer syntax for association lists (an association list is just
@@ -190,7 +212,7 @@ The expression ``#'(arg ...)`` expands to a list of syntax
objects which are then transformed by is the ``syntax-match`` transformer,
which converts identifiers of the form ``n`` into couples of the form
``(n n)``, whereas it leaves couples ``(n v)`` unchanged, however
-by checking that ``n`` is an identifier.
+it checks that ``n`` is an identifier.
Macros as list transformers
---------------------------------------------------------------------
@@ -207,21 +229,21 @@ list: for instance a macro describing function composition
::
- (def-syntax (app f g)
+ (def-syntax (o f g)
#'(f g))
can be written equivalently also as
::
- (def-syntax (app f g)
+ (def-syntax (o f g)
(list #'f #'g))
or
::
- (def-syntax (app f g)
+ (def-syntax (o f g)
(cons* #'f #'g #'()))
The sharp-quoted syntax is more readable, but it hides the underlying list
@@ -232,13 +254,13 @@ macros.
``sweet-macros`` provide a convenient feature:
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:
+here is the transformer associated to the ``o`` macro:
.. code-block:: scheme
- > (define tr (define-a <transformer>))
- > (tr (list #'dummy #'1))
- (#<syntax define> #<syntax a> 1)
+ > (define tr (o <transformer>))
+ > (tr (list #'o #'f #'g))
+ (#<syntax f> #<syntax g> . #<syntax ()>)
Notice that the name of the macro (in this case ``define-a`` is ignored
by the transformer, i.e. it is a dummy identifier.
@@ -276,8 +298,9 @@ by the transformer, i.e. it is a dummy identifier.
;;TEST-ALIST
(test "simple"
- (alist (a 1) (b (* 2 a)))
- '((a 1) (b 2)))
+ (let ((a 0))
+ (alist a (b 1) (c (* 2 b))))
+ '((a 0) (b 1) (c 2)))
;;END
;(test "with-error"
diff --git a/artima/scheme/scheme28.ss b/artima/scheme/scheme28.ss
index 0ef3bb7..32ec794 100644
--- a/artima/scheme/scheme28.ss
+++ b/artima/scheme/scheme28.ss
@@ -12,7 +12,15 @@ easier, by providing a nicer syntax and introspection features.
However now, after more than a dozen
episodes about macros, I can assume my readers are beginners no more,
and it is time to have a look at the larger Scheme world and to
-compare/contrast ``sweet-macro`` with the other systems out there.
+compare/contrast ``sweet-macro`` with the most used systems, i.e.
+``syntax-rules``, ``syntax-case`` and ``define-macro``.
+
+Actually, there a few other intesting options
+out there, such as syntactic closures and systems based on explicit
+renaming. Since I do not want to discuss all the macro systems in existence
+here, I will point out a good resource on the
+subject: this excellent `post by Alex Shinn`_ on the Chicken mailing list
+summarizes the situation better than I could do.
``syntax-match`` vs ``syntax-rules``
-----------------------------------------------------------------
@@ -23,19 +31,19 @@ compare/contrast ``sweet-macro`` with the other systems out there.
(def-syntax (syntax-rules (literal ...) (patt templ) ...)
#'(syntax-match (literal ...) (sub patt #'templ) ...))
-As you see, the difference between ``syntax-rules`` (a part
-for missing the ``sub`` literal)
-is that ``syntax-rules`` automatically adds the syntax-quote ``#'``
-operator to you templates. That means that you cannot use
-quasisyntax tricks and that ``syntax-rules`` is strictly less
-powerful than ``syntax-match``. Moreover, ``syntax-rules`` macros
-do not have guarded patterns; the most direct consequence is that
-providing good error messages for wrong syntaxes is more difficult.
+As you see, the difference between ``syntax-rules`` (a part for
+missing the ``sub`` literal) is that ``syntax-rules`` automatically
+adds the syntax-quote ``#'`` operator to you templates. That means
+that you cannot use quasisyntax tricks and that ``syntax-rules`` is
+strictly less powerful than ``syntax-match``. Moreover,
+``syntax-rules`` macros do not have guarded patterns; the most direct
+consequence is that providing good error messages for wrong syntaxes
+is more difficult.
``syntax-match`` vs ``syntax-case``
-----------------------------------------------------------------
-``syntax-case`` can also be defined in terms of ``syntax-match`` as
+``syntax-case`` could be defined in terms of ``syntax-match`` as
follows::
(def-syntax syntax-case
@@ -46,7 +54,7 @@ follows::
#'(syntax-match x (literal ...) (sub patt skel) ...))
))
-In practice, however, ``syntax-case`` is a Scheme primitive and
+In reality, ``syntax-case`` is a Scheme primitive and
``syntax-match`` is defined on top of it. So, ``syntax-case`` has
theoretically the same power as ``syntax-match``, but in practice
``syntax-match`` is more convenient to use because of the
@@ -58,7 +66,8 @@ the skeleton, whereas in ``syntax-match`` is positioned *after* the skeleton.
Changing the position has cost me a lot of reflection, since I *hate*
gratuitous breaking. However, I am convinced that the position of the
guard in ``syntax-case`` is really broken, so I had to *fix* the issue.
-Why do I say so?
+
+XXX: why do I say so?
``syntax-match`` versus ``define-macro``
---------------------------------------------------------------
@@ -83,6 +92,7 @@ back into a syntax object in the context of the macro.
.. _9: http://www.artima.com/weblogs/viewpost.jsp?thread=240804
.. _On Lisp: http://www.paulgraham.com/onlisp.html
.. _hygiene in R6RS: http://docs.plt-scheme.org/r6rs-lib-std/r6rs-lib-Z-H-13.html#node_sec_12.1
+.. _post by Alex Shinn: http://lists.gnu.org/archive/html/chicken-users/2008-04/msg00013.html
The hygiene problem
---------------------------------------------------------------------
@@ -96,22 +106,20 @@ effects. As Paul Graham puts it,
*errors caused by variable capture are rare, but what they lack
in frequency they make up in viciousness*.
-The hygiene problem is the main reason why `define-macro``
-is becoming less and less used in the Scheme world. PLT
-Scheme has being deprecating it for many years and nowadays
-even Chicken Scheme, which
-traditionally used ``define-macro`` a lot, has removed it from
-the core, by using hygienic macros instead: this is the reason why the
-current Chicken (Chicken 4.0) is called "hygienic
-Chicken".
+The hygiene problem is the main reason why `define-macro`` is becoming
+less and less used in the Scheme world. PLT Scheme has being
+deprecating it for many years and nowadays even Chicken Scheme, which
+traditionally used ``define-macro`` a lot, has removed it from the
+core, by using hygienic macros instead: this is the reason why the
+current Chicken (Chicken 4.0) is called "hygienic Chicken".
You can find good discussions of the hygiene problem in Common Lisp
in many places; I am familiar with Paul Graham's book `On Lisp`_ which
I definitively recommend: the chapter on variable chapter is the best
-reference I know. Another extremely good reference is the chapter
+reference I know. Another good reference is the chapter
about ``syntax-case`` - by Kent Dybvig - in the book `Beautiful Code`_.
-Here I will give just a short example exhibiting the problem, with
-the readers unfamiliar with it.
+Here I will give just a short example exhibiting the problem, for the
+sake of the readers unfamiliar with it.
.. image:: hygienic-paper-small.jpg
@@ -155,6 +163,8 @@ There is an error here because shadowing ``unless`` affects the
means that the macro user is forced to know all the identifiers
that are used internally by the macro.
+.. _Beautiful Code: http://oreilly.com/catalog/9780596510046/
+
Breaking hygiene
-------------------------------------------------
@@ -255,6 +265,11 @@ $$DEF-BOOK
to be used as in the following test:
$$TEST-DEF-BOOK
+
+There are better ways to define records in Scheme, and there is also
+a built-in facility to define record types: you should consider
+``def-book`` just as an example of use of ``identifier-append``,
+not as a recommended pattern to define records.
|#
(import (rnrs) (sweet-macros) (aps lang) (aps list-utils) (aps compat)
diff --git a/artima/scheme/scheme29.ss b/artima/scheme/scheme29.ss
index 82872b0..1245246 100644
--- a/artima/scheme/scheme29.ss
+++ b/artima/scheme/scheme29.ss
@@ -1,244 +1,313 @@
-#|
-Records from macros
-=========================================================================
+#|Comparing identifiers
+==============================================================
-In this episode I show how to introduce auxiliary identifiers in a
-macro, by using the standard R6RS utility
-``generate-temporaries``. As an example, I show how you can define
-record types and I discuss the hygienic feature of Scheme macros.
+What does it mean that two identifiers are equal in a lexically
+scoped language with hygienic macros?
-``generate-temporaries``
------------------------------------------------------
+Take for instance this piece of code:
-The R6RS standard provides a few convenient utilities to work with
-macros. One of such utilities is the ``with-syntax`` form, which
-allows to introduce auxiliary pattern variables into a skeleton
-(a better name would have been ``let-pattern-vars``). Let me make an
-example. ``with-syntax`` is often used in conjunction with the
-``generate-temporaries`` function, which returns a list of temporary
-identifiers.
+.. code-block:: scheme
+
+ (let ((a 1))
+ (let ((a 2))
+ ...))
+
+Is the first ``a`` equal to the second ``a``? They are bound to
+different values, and they may be considered equal or not.
+Moreover, consider the following:
-Here is an example where the temporary variable is used
-as argument in the lambda function: a ``fold`` macro
-providing a nicer syntax for the ``fold-left`` and ``fold-right``
-higher order functions.
+.. code-block:: scheme
-$$list-utils:FOLD
+ (let ((a 1))
+ (let ((b a))
+ ...))
-Notice the usage of the literals ``left`` and ``right`` to avoid
-writing two separated macros, and the usage of ``in`` to enhance
-readability.
+Here ``a`` and ``b`` are different names for the same value. Should they
+be considered equal?
-In this example, for each variable ``x`` a pattern variable ``a`` is
-generated with a temporary name. For instance, in Ypsilon
+Finallly, consider what happens in a macro introducing dummy
+identifiers:
.. code-block:: scheme
- (fold left (s 0) (x in (range 3)) (y in (range 3)) (+ s x y))
+ (def-syntax (macro x)
+ #'(let ((dummy 1))
+ ...))
-expands to
+What happens if I pass to the macro an ``x`` argument named ``dummy``?
+Should it be considered equal to the identifier introduced by the ``let``
+form or not?
-.. code-block:: scheme
- (fold-left
- (lambda (s \x2E;L271 \x2E;L272)
- (let+ (x \x2E;L271) (y \x2E;L272) (+ s x y)))
- 0 (range 3) (range 3))
+As a matter
+of fact the Scheme standard define two different equality procedures
+for identifiers (``bound-identifier=?`` and ``free-identifier=?``); to
+those, I will add a third one, ``raw-identifier=?``:
-as you can check by using ``syntax-expand``. The temporary names are
-quite arbitrary, and you will likely get different names, since each
-time ``generate-temporaries`` is called, different names are
-generated. ``generated-temporaries`` is perfect to generate dummy
-names used as arguments, as seen in this example. Another typical
-usage is to generate dummy names for helper functions, as shown in the
-following paragraph.
+$$lang:RAW-IDENTIFIER=?
-A record type macro
----------------------------------------------------------------
+This episode will be spent discussing the subtilities of identifier
+equality.
-Scheme has a vector data type, which is used to manage finite sequences
-with a fixed number *n* of values, known at compile time. Each element
-can be accessed in O(1) time by specifying an integer index starting from
-*0* to *n*, with the notation ``(vector-ref v i)``. Vectors are perfect
-to implement records, since you can see a record as a vector with *n+1*
-arguments, where the 0-th element specify the type of the vector
-and the i-th element is the i-th field of the record.
-Notice that the stardard specifies a record system, but writing a
-record system based on macros is a good exercise nonetheless.
-It also provides a good example of a second order macro expanding
-to a macro. Here is the code:
+Using ``raw-identifier=?``
+-----------------------------------------------
-$$DEF-RECORD-TYPE
+The simplest procedure by far is ``raw-identifier=?``: two
+identifiers are ``raw-identifier=?`` if they are equal as symbols.
+For convenience, I have added the
+``raw-identifier=?`` precedure in the ``(aps lang)`` library.
+``raw-identifier=?`` can be used to manage duplicate names in
+macros defining multiple identifiers at once, or in macros
+defining tables names->values, such as the ``static-map``
+we discussed in episode 22.
-.. image:: vinyl-record.png
+$$STATIC-MAP
-An example will make everything clear. Suppose we want to define a
-``Book`` record type; we can do so by writing
+Using ``bound-identifier=?``
+-----------------------------------------------
-``(def-record-type Book title author)``
+``raw-identifier=?`` is simple and easy to understand, but it cannot
+be used in all situations. Consider for instance the very first macro
+we wrote, in episode 9_:
-which expands to (in Ikarus):
+.. code-block:: scheme
+
+ (def-syntax (multi-define (name1 name2 ...) (value1 value2 ...))
+ #'(begin (define name1 value1) (define name2 value2) ...))
+
+It is quite common to write macros defining multiple bindings, such
+as ``multi-define``. There is also the common situation of
+macros which expand themselves to macros defining
+multiple bindings. When code is generated automatically, errors are
+more than likely and it makes sense to manage explicitly
+the situation when the list of names to be defined contains some
+duplicate. ``multi-define`` as written does not perform any check, so that it
+relies on the standard behavior of R6RS scheme, raising an error.
+Unfortunately, the standard behavior only applies to programs and scripts,
+whereas the REPL is quite free to behaves differently and indeed it does:
.. code-block:: scheme
- (begin
- (def-syntax Book
- (syntax-match (New Signature ? title author)
- (sub (Book New) #'record-new)
- (sub (Book Signature) #''(Book title author))
- (sub (Book ?) #'record?)
- (sub (Book title) #'#{title |Ivhf4sEgOry2IG%W|})
- (sub (Book author) #'#{author |r=hJyxJbHsP$j&$3|})))
- (define (record-new title author)
- (vector 'Book title author))
- (define (record? b) (eq? 'Book (vector-ref b 0)))
- (define (#{title |Ivhf4sEgOry2IG%W|} b)
- (assert (record? b))
- (vector-ref b 1))
- (define (#{author |r=hJyxJbHsP$j&$3|} b)
- (assert (record? b))
- (vector-ref b 2)))
-
-This code defines a ``Book`` macro and a few auxiliary functions such
-as ``record-new``, ``record?`` and two others with temporary names.
-The temporary names are of course implementation-specific. Ikarus
-here does a nice thing by prefixing them with the names coming
-from the list given as argument to ``generate-temporaries``, so
-that you can ascertain their origin.
-
-The ``Book`` macro allows to create new records
-
-::
-
- > (define book ((Book New) "title" "author"))
- > book
- #(Book "title" "author")
-
-to introspect records
-
-::
-
- > ((Book ?) book)
+ > (multi-define (a a) (1 2))
+ a
+ 2
+
+(in the REPL latter definitions override previous definitions). Being
+unhappy with that, we can introduce a ``bound-identifier=?`` check
+and raise a custom exception:
+
+.. code-block:: scheme
+
+ > (multi-define (a a) (1 2))
+ Unhandled exception
+ Condition components:
+ 1. &who: multi-define
+ 2. &message: "Found duplicated identifier in"
+ 3. &syntax:
+ form: (a a)
+ subform: #f
+
+This is a case where using ``raw-identifier=?`` would not work.
+Here is a (made-up) example explaining the problem with ``raw-identifier=?``
+in the presence of dummy identifiers introduced by macros. Consider
+the following macro expanding to ``multi-define``:
+
+$$MULTI-DEFINE2
+
+The macro introduces a dummy identifier ``id2``. What happens if
+we call ``multi-define2`` with argument ``id`` equal to ``id2``?
+
+ > (multi-define2 id2 1)
+ id2
+ 1
+
+The answer is nothing, since we defined ``multi-define`` in terms of
+``bound-identifier=?``, and two identifiers are equal according to
+``bound-identifier=?`` only if they have the same name *and* the same
+marks. In this case the identifier ``id2`` introduced by the macro
+has different marks from the identifier ``id2`` used as an argument
+in the macro. Had we defined ``multi-define`` in
+terms of ``raw-identifier=?``, we would have had a spurious name
+clash.
+
+Using ``free-identifier=?``
+------------------------------------
+
+``free-identifier=?`` is the trickiest equality predicate. It is able
+to determinate if two bound identifiers are the same apart possibly for
+a rename at import time:
+
+.. code-block:: scheme
+
+ > (import (only (aps list-utils) range))
+ > (import (rename (aps list-utils) (range r)))
+ > (free-identifier=? #'r #'range)
#t
- > (Book Signature)
- (book title author)
-
-and to retrieve the elements of a record by field name::
-
- > ((Book title) book)
- "title"
-
- > ((Book author) book)
- "author"
-
-Since I am a fan of functional programming, I am not providing mutation
-methods, so that you may regard them as immutable records (actually
-they are not, since you can change them by using ``vector-set!``,
-but that would be a dirty trick ;)
-
-Notice that a record system like the one presented here features
-record types which are not first class objects - since they are
-macros; in this respect it
-is more similar to the type system of languages like SML, where types
-are not objects, and very different from a type system like the Python
-one, where classes are objects. Of course in Scheme you can also
-implement a Python-like object system, where it is possible to create
-dynamic record types at runtime and not only at compile time. You can
-implement it yourself, or wait for a future episode ;)
-
-.. hygiene in R6RS: http://docs.plt-scheme.org/r6rs-lib-std/r6rs-lib-Z-H-13.html#node_sec_12.1
-
-There is a subtle point about the ``def-record-type`` macro defined in
-the previous paragraph. Such a macro introduces a lots of auxiliary
-functions, such as ``record-new``, ``record?``, and an accessor
-function with a temporary name for every record field. One may expect
-those names to litter the namespace: i.e., after expansion, you would
-expect the names ``record-new``, ``record?`` and the temporary names
-to be defined in the namespaces. Actually this is not the case:
-Scheme macros are *hygienic*, and auxiliary names introduced in the
-macro *are not visible outside*.
-
-This is a major difference with respect to Common Lisp macros.
-The only names which enter in the namespace are the ones we put in;
-in the case of ``def-record-type`` only the name of the record type (i.e.
-``Book``) enters in the namespace after macro expansion. Nonetheless,
-the auxiliary names are known to ``Book`` macro, and for instance
-``(Book ?)`` will expand to the right ``record?`` function.
-
-Everything works even if
-in the same module you define a different record type with a different
-``record?`` function: there will be no nameclashes. The reason is that
-the implementation of macros takes care of distinguishing the
-names in some way (it could be based on marking the names, or on
-explicit renaming).
-
-In particular, in the ``def-record-type``
-macro, I should notice that I have been able to use the name ``record?``
-only because is an internal name: if the macroexpansion were literal,
-I would have incurred in a name clash, since ``record?`` is a builtin
-name.
+Both ``raw-identifier=?`` and ``bound-identifier=?`` would fail to
+recognize the identity of ``range`` and ``r`` in this case.
+Notice that two aliases (same name, same binding) are *not*
+considered ``free-identifier=?``:
+
+.. code-block:: scheme
+
+ > (define a 1)
+ > (define b a) ;; alias for a
+ > (free-identifier=? #'a #'b)
+ #f
+
+Moreover
+
+For things like cond which need to distinguish macro-specific literals
+from bound names (e.g.: (let ((else 1)) (cond (else ---)))),
+free-identifier=? is the right predicate.
+
+For your convenience I have defined a ``compare-ids`` macro that can be
+used to determine how two identifiers compare with respect to the different
+equality operators:
+
+$$COMPARE-IDS
+
+(notice the usage of ``syntax->datum``: a list of literals
+is generated, quoted and then converted into a syntax
+object in the context of the macro, so that
+``(compare-ids id1 id2)`` expands into a list of literals).
+
+Auxiliary keywords
+----------------------------------
+
+The R6RS document defines a set of special macros, such as ``_``, ``...``,
+``else`` and ``=>``, which lives in the global namespace and are
+available to all R6RS programs. Such macros are used as auxiliary
+syntax in various special forms, like ``cond`` and ``syntax-case``;
+for this reason they are usually called auxiliary keywords.
+The existence of such global variables makes it impossible to redefine
+at top-level in scripts (but it can be done at the REPL);
+however they can be redefined locally, thus breaking
+the macros using the auxiliary syntax.
+
+.. code-block:: scheme
+
+ (import (rnrs))
+ (display
+ (let ((else #f))
+ (cond (else 2))))
+
|#
-(import (rnrs) (sweet-macros) (for (aps lang) run expand)
- (aps easy-test) (for (aps list-utils) run expand) (aps compat))
-
-;;DEF-RECORD-TYPE
-(def-syntax (def-record-type name field ...)
- (: with-syntax
- (getter ...) (generate-temporaries #'(field ...))
- (i ...) (range 1 (+ (length #'(field ...)) 1))
- #`(begin
- (def-syntax name
- (syntax-match (New Signature ? field ...)
- (sub (name New) #'record-new)
- (sub (name Signature) #''(name field ...))
- (sub (name ?) #'record?)
- (sub (name field) #'getter)
- ...))
- (define (record-new field ...) (vector 'name field ...))
- (define (record? b) (eq? 'name (vector-ref b 0)))
- (define (getter b) (assert (record? b)) (vector-ref b i)) ...
- )))
+(import (rnrs) (sweet-macros) (aps list-utils) (aps lang) (aps easy-test))
+
+;;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
-;;RECORD
-(def-syntax (record-syntax name field ...)
- (: with-syntax
- (getter ...) (generate-temporaries #'(field ...))
- (i ...) (range 1 (+ (length #'(field ...)) 1))
- #`(let ()
- (define (record-new field ...) (vector 'name field ...))
- (define (record? b) (eq? 'name (vector-ref b 0)))
- (define (getter b) (assert (record? b)) (vector-ref b i))
- ...
- (syntax-match (New Signature ? field ...)
- (sub (name New) #'record-new)
- (sub (name Signature) #''(name field ...))
- (sub (name ?) #'record?)
- (sub (name field) #'getter)
- ...))))
+;;MULTI-DEFINE
+(def-syntax (multi-define (name1 name2 ...) (value1 value2 ...))
+ #'(begin (define name1 value1) (define name2 value2) ...)
+ (distinct? bound-identifier=? #'(name1 name2 ...))
+ (syntax-violation 'multi-define "Found duplicated identifier in"
+ #'(name1 name2 ...)))
;;END
-
-;(def-syntax Book (record-syntax Book title author))
-;(pretty-print (syntax-expand (record-syntax Book title author)))
+;;MULTI-DEFINE2
+(def-syntax (multi-define2 id value)
+ #'(multi-define (id id2) (value 'dummy)))
+;;END
-(def-record-type Book title author)
+;;STATIC-MAP
+(def-syntax (static-map (name value) ...)
+ #'(syntax-match (<names> name ...)
+ (sub (ctx <names>) #''(name ...))
+ (sub (ctx name) #'value)
+ ...)
+ (distinct? raw-identifier=? #'(name ...))
+ (syntax-violation 'static-map "Found duplicated identifier in"
+ #'(name ...)))
+;;END
-(pretty-print (syntax-expand (def-record-type Book title author)))
+;;COMPARE-IDS
+(def-syntax (compare-ids id1 id2)
+ (let ((res '()))
+ (when (raw-identifier=? #'id1 #'id2)
+ (set! res (cons 'raw= res)))
+ (when (bound-identifier=? #'id1 #'id2)
+ (set! res (cons 'bound= res)))
+ (when (free-identifier=? #'id1 #'id2)
+ (set! res (cons 'free= res)))
+ (datum->syntax #'compare-ids `',res)))
+;; END
+
+(def-syntax (compare id1 a id2 b)
+ #'(let ((id1 a))
+ (let ((id2 b))
+ (compare-ids id1 id2))))
+
+(multi-define (a b) (1 2))
+
+;(import (rnrs) (sweet-macros) (aps list-utils))
+
+(def-syntax (free-bound= id1 id2)
+ #`(list #,(free-identifier=? #'id1 #'id2)
+ #,(bound-identifier=? #'id1 #'id2)))
+
+(display
+ (let ([fred 17])
+ (def-syntax (compare-with-fred id)
+ #'(free-bound= fred id))
+ (compare-with-fred fred))) ;=> (#t #f)
+
+;;EXPAND-X
+(def-syntax expand-x
+ (syntax-match ()
+ (sub (expand-x id) "bound x" (bound-identifier=? #'id #'x))
+ (sub (expand-x id) "free x" (free-identifier=? #'id #'x))
+ (sub (expand-x id) "other")))
+;;END
-(define b ((Book New) "T" "A"))
-(display b)
-(newline)
-(display (Book Signature))
-(display ((Book ?) b))
+(run
+ (test "free identifier"
+ (expand-x x)
+ "free x")
+ (test "non-free identifier"
+ (let ((x 1)) (display (expand-x x)))
+ "other")
+ (test "other identifier"
+ (expand-x y)
+ "other"))
+(define (free-bound-x id)
+ (list (free-identifier=? #'x id) (bound-identifier=? #'x id)))
+
+(def-syntax (is-a id)
+ (free-identifier=? #'id #'a))
+
+(display
+ (let ()
+ (list (is-a x) (is-a a))))
+
+(display
+ (let* ((a 2) (x a))
+ (list (is-a x) (is-a a))))
-(display (syntax-expand (Book title)))
-(newline)
-(display ((Book title) b))
(newline)
-(display ((Book author) b))
+(display (compare a 1 b a))
+
+(display (let ((a 1) (b 2))
+ (compare-ids a b)))
+
+(display (let ((a 1) (b 1))
+ (compare-ids a b)))
+
+(display (let ((a 1) (b a))
+ (compare-ids a b)))
+
+(display (let ((a 1))
+ (compare-ids a a)))
diff --git a/artima/scheme/scheme30.ss b/artima/scheme/scheme30.ss
new file mode 100644
index 0000000..7bae815
--- /dev/null
+++ b/artima/scheme/scheme30.ss
@@ -0,0 +1,264 @@
+#|Runtime pattern matching and object oriented APIs
+=======================================================
+
+As a final example of macros power, this episode will implement a syntax
+for full runtime pattern matching of lists .
+
+.. _13: http://www.artima.com/weblogs/viewpost.jsp?thread=248953
+.. _15: http://www.artima.com/weblogs/viewpost.jsp?thread=249681
+.. _association lists:
+
+list-match
+----------------------------------------------------------------
+
+In episode 15_ I have discussed a very limited form of pattern
+matching, i.e. list destructuring. There is more to
+pattern matching than that.
+
+A general list matcher should be able to
+destructure a list, whatever complicate it may be, and to perform
+*different* actions for different structures. In this sense, pattern
+matching is a kind of if statement on steroids.
+
+The Scheme standard does not provide a default list matcher, but there
+are tons of libraries providing such a feature, and in the appendix
+we will provide an implementation of ``list-match`` ourselves, so
+let us assume we have list matcher accepting the following patterns:
+
+.. code-block:: scheme
+
+ > (list-match <patterns>)
+ ((list-match lst (sub pattern action guard ...) ...))
+
+Given an object (which usually is a list), the matcher compares
+it with various patterns, and perform different actions depending
+on the match, which can be further restricted with a guard.
+Here are a few trivial tests:
+
+$$MATCH-TESTS
+
+As usual, the understore ``_`` means "match but do not bind.
+Here are two further tests, that makes use of the following helper function:
+
+$$MATCH-ODD
+
+``replace-odd`` makes use of guarded patterns to replace the odd
+numbers in a list of numbers with the string "odd":
+
+$$MATCH-ODD1
+
+``replace-odd`` only recognizes numbers and lists: it chokes on strings
+or any other kind of object, by raising a ``pattern failure`` error:
+
+$$MATCH-ODD2
+
+This example is simple, but it does not make justice to the power of
+pattern matching. The following example, however, will do (or at least
+I hope so): it explains how to implement object oriented APIs in terms
+of pattern matching functions.
+
+Pattern matching vs message passing
+-------------------------------------------------------
+
+A typical use case for
+full pattern matching is message passing: in functional languages you
+can send lists as messages to an object, and the object will respond
+differently depending on the structure of the list. This is the
+standard mechanism to send messages in Erlang, but can be done in any
+functional language with pattern matching.
+
+I you think about it for a minute, you will recognize that pattern
+matching makes the creation of object oriented APIs rather trivial.
+Let consider for instance Scheme `association lists`_, which by default
+have a functional API, and suppose we want to provide an object oriented
+interface on top on it, say a Python-like API. The task can be easily
+performed via pattern matching, as follows:
+
+$$ALIST
+
+Here we used the R6RS procedures ``(assq k a)`` to extract the slot
+corresponding to the key ``k`` from the association list ``a``, as
+well as the procedure ``set-cdr!`` described in episode 13_ to modify
+the ``cdr`` of a slot (imported from ``(rnrs mutable-pairs)``).
+Notice that I am using the term *slot* here to refer to
+the inner lists that compose an association list.
+
+Here are a few tests:
+
+$$ALIST-TESTS
+
+Appendix: implementation of ``list-match``
+-------------------------------------------------------------
+
+The implementation below is not my own, I have adapted
+(i.e. shamelessly copied) it from Phil Bewig, which in turns copied it
+from Jos Koot, which maybe is the original author, or maybe he copied
+it from some other source. But attribution is not important, the
+important thing is the code:
+
+$$aps/list-match:
+
+The implementation above makes use of a few tricks which are well known
+to experienced Schemers, but may look unfamiliar to my readers.
+
+First of all, we leverage on the fact that ``(and boolean expr)`` returns
+``expr`` if ``boolean`` is true and ``#f`` otherwise, i.e. it is equivalent
+to ``(if boolean expr #f)``. Then, we use the ``=>`` form of the conditional
+expression.
+As explained in the `cond specification`, a ``cond`` clause may
+have the form ``(value => func)``; when the value is not false,
+then the function is applied to it and ``cond`` returns the result,
+otherwise ``cond`` looks at the next clause. For instance
+
+.. code-block:: scheme
+
+ > (cond (#f => any-function) ('(1) => car))
+ 1
+
+Notice that the underscore is a special identifier in macros
+(just as the ellipsis identifier ``...``` is special) and it
+cannot be used as a literal, this is why the trick
+``(free-identifier=? #'_ underscore)`` is used.
+
+I have exported the internal macro ``_match``, to make it
+possible to understand how the implementation works. ``_match`` is
+able to match a single pattern. For instance we have
+
+.. code-block:: scheme
+
+ > (_match '(1 2) (x y) (list (+ x y)))
+ (3)
+
+Using ``syntax-expand`` we can see the inner working of ``_match``:
+
+.. code-block:: scheme
+
+ > (syntax-expand(_match '(1 2) (x y) (list (+ x y)) #t))
+ (let ((ob '(1 2)))
+ (and (pair? ob)
+ (let ((kar-obj ob) (kdr-obj (cdr ob)))
+ (_match kar-obj x
+ (_match kdr-obj (y) (list (+ x y)) #t)))))
+
+
+.. _cond specification: http://www.r6rs.org/final/html/r6rs/r6rs-Z-H-14.html#node_idx_376
+
+|#
+
+(import (rnrs) (sweet-macros) (aps list-utils) (aps list-match)
+ (rnrs mutable-pairs) (aps easy-test) (aps compat))
+
+;;ALIST
+;; a function performing pattern matching which provides a Pythonic
+;; dictionary interface over an association list
+(define (alist . a)
+ (lambda args
+ (list-match args
+ (sub () a); return the underlying alist
+ (sub ('->keys); return the keys
+ (map car a))
+ (sub ('->values); return the values
+ (map cdr a))
+ (sub (k); emulate Python .__getitem__
+ (let ((slot (assq k a)))
+ (if slot
+ (cdr slot)
+ (error 'alist (format "Missing key ~a" k)))))
+ (sub (k 'or val); emulate Python .get
+ (let ((slot (assq k a)))
+ (if slot
+ (cdr slot)
+ val)))
+ (sub (k '! val); emulate Python .__setitem__
+ (let ((slot (assq k a)))
+ (if slot
+ ;; modify existing slot
+ (begin (set-cdr! slot val) a)
+ ;; else append new slot
+ (let ((new-a (append a (list (cons k val)))))
+ (set! a new-a) a))))
+ )))
+;;END
+
+(define a (alist '(x . 1) '(y . 2)))
+
+;;MATCH-ODD
+(define (replace-odd obj)
+ (list-match obj
+ (sub x "odd" (and (integer? x) (odd? x)))
+ (sub x x (integer? x))
+ (sub x (map replace-odd x) (list? x))
+ ))
+;;END
+
+(run
+
+ (test "patterns"
+ (list-match <patterns>)
+ '((list-match lst (sub pattern template guard ...) ...)))
+
+ ;;MATCH-TESTS
+ (test "match1"
+ (list-match '(1) (sub (x) x))
+ 1)
+
+ (test "match2"
+ (list-match '(1 2) (sub (_ y) y))
+ 2)
+
+ (test "match3"
+ (list-match '(1 (2 3)) (sub (_ (_ z)) z))
+ 3)
+ ;;END
+
+ ;;MATCH-ODD1
+ (test "match-odd1"
+ (replace-odd '(1 (2 3)))
+ '("odd" (2 "odd")))
+ ;;END
+
+ ;;MATCH-ODD2
+ (test "match-odd2"
+ (catch-error (replace-odd "string"))
+ "pattern failure")
+ ;;END
+
+ ;;ALIST-TESTS
+ (test "alist1"
+ (a 'x)
+ 1)
+
+ (test "alist2"
+ (a 'y)
+ 2)
+
+ (test "alist3"
+ (catch-error (a 'z))
+ "Missing key z")
+
+ (test "alist4"
+ (a 'z 'or 3)
+ 3)
+
+ (test "alist5"
+ (a 'z '! 3)
+ '((x . 1) (y . 2) (z . 3)))
+
+ (test "alist6"
+ (a 'y '! 4)
+ '((x . 1) (y . 4) (z . 3)))
+
+ (test "alist7"
+ (a '->keys)
+ '(x y z))
+
+ (test "alist8"
+ (a '->values)
+ '(1 4 3))
+ ;;END
+ )
+
+; (test "match4"
+; (list-match '(1 (2 (x 4) 5) 6)
+; (sub x (and (integer? x) (odd? x)) "odd")
+; (sub x (and (integer? x) (odd? x)) "odd")) 1)