diff options
author | michele.simionato <devnull@localhost> | 2009-06-12 15:23:11 +0000 |
---|---|---|
committer | michele.simionato <devnull@localhost> | 2009-06-12 15:23:11 +0000 |
commit | 1edb00ff4c434771ce93976a2b4497f94179503a (patch) | |
tree | d210e100740593b8694415252f17273d7c417554 /artima | |
parent | 6e250c8e4cd545d829f562242acafef06234f6b3 (diff) | |
download | micheles-1edb00ff4c434771ce93976a2b4497f94179503a.tar.gz |
Lots of work on my adventures
Diffstat (limited to 'artima')
-rw-r--r-- | artima/notes/links-and-citations.txt | 9 | ||||
-rw-r--r-- | artima/scheme/Makefile | 6 | ||||
-rw-r--r-- | artima/scheme/scarti.txt | 8 | ||||
-rw-r--r-- | artima/scheme/scheme20.ss | 12 | ||||
-rw-r--r-- | artima/scheme/scheme27.ss | 123 | ||||
-rw-r--r-- | artima/scheme/scheme28.ss | 61 | ||||
-rw-r--r-- | artima/scheme/scheme29.ss | 477 | ||||
-rw-r--r-- | artima/scheme/scheme30.ss | 264 |
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) |