diff options
author | michele.simionato <devnull@localhost> | 2009-06-02 13:40:46 +0000 |
---|---|---|
committer | michele.simionato <devnull@localhost> | 2009-06-02 13:40:46 +0000 |
commit | 01b53681d84a0da8af948553674c66e1f45e04e0 (patch) | |
tree | ef93f3db3506bf752944ebfa6cee709d2c09ddcc /artima | |
parent | 1acfc8e3c06f500c738d8550def3ac16ece77dc7 (diff) | |
download | micheles-01b53681d84a0da8af948553674c66e1f45e04e0.tar.gz |
Fixed a lot of misprints signaled by Marco Maggi
Diffstat (limited to 'artima')
-rw-r--r-- | artima/scheme/Makefile | 7 | ||||
-rw-r--r-- | artima/scheme/scarti.txt | 29 | ||||
-rw-r--r-- | artima/scheme/scheme11.ss | 10 | ||||
-rw-r--r-- | artima/scheme/scheme12.ss | 18 | ||||
-rw-r--r-- | artima/scheme/scheme13.ss | 18 | ||||
-rw-r--r-- | artima/scheme/scheme19.ss | 3 | ||||
-rw-r--r-- | artima/scheme/scheme2.ss | 14 | ||||
-rw-r--r-- | artima/scheme/scheme20.ss | 23 | ||||
-rw-r--r-- | artima/scheme/scheme25.ss | 117 | ||||
-rw-r--r-- | artima/scheme/scheme26.ss | 208 | ||||
-rw-r--r-- | artima/scheme/scheme27.ss | 10 | ||||
-rw-r--r-- | artima/scheme/scheme28.ss (renamed from artima/scheme/macro4.ss) | 132 | ||||
-rw-r--r-- | artima/scheme/scheme29.ss (renamed from artima/scheme/macro5.ss) | 57 | ||||
-rw-r--r-- | artima/scheme/scheme3.ss | 64 | ||||
-rw-r--r-- | artima/scheme/scheme5.ss | 42 | ||||
-rw-r--r-- | artima/scheme/scheme7.ss | 18 | ||||
-rw-r--r-- | artima/scheme/scheme8.ss | 20 | ||||
-rw-r--r-- | artima/scheme/table-of-contents.txt | 2 |
18 files changed, 456 insertions, 336 deletions
diff --git a/artima/scheme/Makefile b/artima/scheme/Makefile index 925e8bc..ae94d9a 100644 --- a/artima/scheme/Makefile +++ b/artima/scheme/Makefile @@ -143,6 +143,9 @@ table-of-contents: table-of-contents.txt fourth-cycle: fourth-cycle.txt $(POST) fourth-cycle.txt 253071 +eurolisp2: eurolisp2.txt + $(POST) eurolisp2.txt 259100 + 19: scheme19.ss $(RST) scheme19.ss; $(POST) scheme19.rst 251476 @@ -160,3 +163,7 @@ fourth-cycle: fourth-cycle.txt 24: scheme24.ss $(RST) scheme24.ss; $(POST) scheme24.rst 258103 + +25: scheme25.ss + $(RST) scheme25.ss; $(POST) scheme25.rst 258580 + diff --git a/artima/scheme/scarti.txt b/artima/scheme/scarti.txt index 65315d4..edfa2b2 100644 --- a/artima/scheme/scarti.txt +++ b/artima/scheme/scarti.txt @@ -767,11 +767,12 @@ This is *wanted* behavior. According to Matthew Flatt: the same way that the compiler complains when a module contains a free variable.* -However I am not convinced by this argument. Whereas it is true that one should -be very careful when mixing compile-time and run-time, especially in -presence of side effects, I believe one can solve the issue by using -separate compilation, and that the benefits of the tower of meta-levels are not -compensated by the complications it induces. +However I am not convinced by this argument. Whereas it is true that +one should be very careful when mixing compile-time and run-time, +especially in presence of side effects, I believe one can solve the +issue by using separate compilation, and that the benefits of the +tower of meta-levels are not compensated by the complications it +induces. My wishlist for a Scheme implementation @@ -805,15 +806,15 @@ I have a personal wishlist of features. - missing features from the standard - I think the standard lacks many important features. I have - already noticed that I would welcome the ability to export all names from a - library ``(export *)`` and the ability to introspect the names exported - by library. In addition, - I would welcome a mechanism to write helper functions for macros *in the - same file* the macros are defined, if one wants to. One way to implement this - feature is to standardize the ability of writing multiple libraries in - the same file, which is already provided by some implementations. - Another way is to introduce at ``define-at-all-phases`` form. + I think the standard lacks many important features. I have already + noticed that I would welcome the ability to export all names from a + library ``(export *)`` and the ability to introspect the names + exported by library. In addition, I would welcome a mechanism to + write helper functions for macros *in the same file* the macros are + defined, if one wants to. One way to implement this feature is to + standardize the ability of writing multiple libraries in the same + file, which is already provided by some implementations. Another + way is to introduce at ``define-at-all-phases`` form. I have not even explained why one would like to diff --git a/artima/scheme/scheme11.ss b/artima/scheme/scheme11.ss index 4063ed7..96214eb 100644 --- a/artima/scheme/scheme11.ss +++ b/artima/scheme/scheme11.ss @@ -15,8 +15,8 @@ a C-like ``for`` loop and I said that it was suffering from the problem of multiple evaluation. Here I explain what the problem is and how to cure it. In order to understand the issue, you must always remember that macros *expand* code at compile time, -but they not *evaluate* it: that means that pattern variables do *not* -correspond to evalued expression, as ordinary variables, but they +but they not *evaluate* it: this means that pattern variables do *not* +correspond to evaluated expression, as ordinary variables, but they correspond to expressions to be evaluated later, at runtime. As a consequence, it is easy to write macros @@ -37,7 +37,7 @@ with a computation:: (printf "computing the value of end\n") 3) -Then our naive macro suffers for the multiple evaluation problem:: +Then our naive macro suffers from the multiple evaluation problem:: > (for i 0 (get-end) 'do-nothing) computing the value of end @@ -103,7 +103,7 @@ $$repeat-benchmark: I took the number ``n`` from the command line arguments in order to fool the compiler: if I hard coded ``(+ 1 1)``, the compiler would replace it with 2 at compilation time, therefore not performing -the computation! (in the original version of this episode I made that +the computation! (In the original version of this episode I made that mistake, thanks to Aziz Ghuloum for pointing it out). The output of the script is the following:: @@ -152,7 +152,7 @@ from the macros we have defined until now. The reason why the ``test`` macro is different is that it expands into a ``lambda``-expression and therefore the arguments of the macro are evaluated only when the ``lambda`` function is called -and not a definition time. In other words, we are using a pattern of +and not at definition time. In other words, we are using a pattern of *delayed evaluation* here. This is important, since we want to distinguish the definition of a test from its execution. For instance, let me define a trivial test:: diff --git a/artima/scheme/scheme12.ss b/artima/scheme/scheme12.ss index c16aa1e..3c54ab6 100644 --- a/artima/scheme/scheme12.ss +++ b/artima/scheme/scheme12.ss @@ -44,7 +44,7 @@ The problem is that it is very easy to abuse the mechanism. Generally speaking, the adaptibility of the Scheme language is a double-edged sword. There is no doubts that it increases the programmer expressivity, but it can also make programs more difficult to -read. The language allows you to invent your own idioms that +read. The language allow you to invent your own idioms that nobody else uses, but perhaps this is not such a good idea if you care about other people reading your code. For this reason macros in the Python community have always been viewed with suspicion: I am also @@ -74,7 +74,7 @@ to that episode, whereas it is argued that it is impossible to implement an equivalent functionality in Python. So, you should not underestimate the power of macros; on the other -hand, you should also not underestimate the complication of macros. +hand, you should also not underestimate the complexity of macros. Recently I have started a `thread on comp.lang.scheme`_ with 180+ messages about the issues I have encountered when porting my ``sweet-macros`` library between different Scheme implementations, and the thread ended @@ -97,11 +97,11 @@ each collection a little bit different from the other. For instance, when I started learning Scheme I wrote a lot of utilities; -later one, I find out that I could find the same utilites, under +later on, I discovered that I could find the same utilites, under different names and slightly different signatures, in various Scheme -frameworks. This never happened to me in Python to the same extend, +frameworks. This never happened to me in Python to the same extent, since the standard library is already coding in an uniform way most of -the useful idioms, so that everybody use the library and there is +the useful idioms, so that everybody uses the library and there is less need to reinvent the wheel. On the other hand, I am not a macro aficionado like Paul Graham, who says: @@ -169,13 +169,13 @@ There are good use cases for macros, but there also plenty of workarounds for the average application programmer. For instance, a case where one could argue for macros, is when there -are performance issue, since macros are able to lift computations from -the runtime to the compile time, and they can be used for code +are performance issues, since macros are able to lift computations from +runtime to compile time, and they can be used for code optimization. However, even without macros, there is plenty of room for optimization in the scripting language world, which typically involve interfacing with C/C++. -There also various standard techniques for *code generation* in C, +There are also various standard techniques for *code generation* in C, whereas C++ has the infamous *templates*: while those are solutions very much inferior to Scheme macros, they also have the enormous advantage of working with an enterprise-familiar technology, and you @@ -183,7 +183,7 @@ certainly cannot say that for Scheme. The other good use for macros is to implement compile time checks: compile time checks are a good thing, but in practice people have -learned to live without by relying on a good unit test coverage, which +learned to live without them by relying on a good unit test coverage, which is needed anyway. On the other hand, one should not underestimate the downsides of diff --git a/artima/scheme/scheme13.ss b/artima/scheme/scheme13.ss index 9175852..c1a03d2 100644 --- a/artima/scheme/scheme13.ss +++ b/artima/scheme/scheme13.ss @@ -29,7 +29,7 @@ I would not consider Python and Common Lisp as truly functional languages: they are just imperative languages with some support for functional programming (I mean constructs such as ``map``, ``filter``, ``reduce``, list comprehension, generators, et cetera). -However, there is large gap between an +However, there is a large gap between an imperative language with some support for functional programming and a true functional language. @@ -46,7 +46,7 @@ you avoid rebinding and you restrict yourself to functional data structures, and it allows many typical idioms of functional programming which have no counterpart in imperative programming. We already saw a common trick in `episode #5`_, i.e. the accumulator trick, which is a -way to avoid mutation in loops by using recursion. In this episodes and +way to avoid mutation in loops by using recursion. In this episode and the next ones we will show many others. .. figure:: diamond.jpg @@ -71,7 +71,7 @@ Actually, it is still possible to mutate pairs, but only by extending the rnrs environment, i.e. by importing the ``(rnrs mutable-pairs)`` extension, which is part of the standard library but not of the core language. This is a clear indication of the fact -that the functional paradigm is somewhat recommended paradigm in Scheme, +that the functional paradigm is somewhat a recommended paradigm in Scheme, even if it is not enforced (in strongly functional languages, such as SML and Haskell, lists are immutable and there is no other option). @@ -79,7 +79,7 @@ I should notice that the requirement of importing ``(rnrs mutable-pairs)`` is only valid for scripts and libraries: the behavior of the REPL is unspecified in the R6RS document (actually the R6RS forbids a REPL but every R6RS implementation provides a REPL with some -different semantics) and implementations are free to import or to not +different semantics) and implementations are free to import or not to import ``mutable-pairs`` in the REPL. The REPL of Ikarus imports ``mutable-pairs`` by default, so you have at your disposal ``set-car!`` and ``set-cdr!``, but this is an implementation specific @@ -114,7 +114,7 @@ immutable data structures becomes easier to understand and to debug. For instance, consider a routine taking a list in input and suppose that the content of the list is not what you would expect. If the list is mutable you potentially have to -wonder about you whole code base, since everything could have mutated +wonder about your whole code base, since everything could have mutated the list before reaching the routine you are interested in. If the list if immutable, you are sure that the bug must be in the procedure which created the list, and in no @@ -164,7 +164,7 @@ The trick is to use recursion:: > (list-set 2 '(a b c d) 'X) (a b X d) -Notice that ``list-set`` is nicer that its imperative counterpart:: +Notice that ``list-set`` is nicer than its imperative counterpart:: (define (list-set! n lst value) (set-car! (list-tail lst n) value) lst) @@ -188,7 +188,7 @@ out of the box ( ``list-set!`` is not in the standard since it is of very little utility: if you want to be able to modify the n-th element of a sequence, you are much better off by using a vector and not a list). -However, it does provide primitive to remove elements for a list +However, it does provide primitives to remove elements for a list functionally:: > (remp even? '(3 1 4 1 5 9 2 6 5)); complementary of filter @@ -205,7 +205,7 @@ functionally:: (you can find other `list utilities`_ in R6RS standard library). -One would expect functional update to be much slower that imperative +One would expect functional update to be much slower than imperative update, because of the need of creating potentially long lists to modify just a single element. However, this is not necessarily true. It all depends on how smart your compiler is. A smart compiler @@ -215,7 +215,7 @@ imperative code. The advantage is that mutation is managed by the compiler, not by the programmer, who can think purely in terms of immutable data structures. -It is true that compiler for functional languages are still slower +It is true that compilers for functional languages are still slower than C compilers in average, but they are not so bad. In most situations the slowdown is acceptable and in particular situations compilers for functional languages can be faster than a C compiler. diff --git a/artima/scheme/scheme19.ss b/artima/scheme/scheme19.ss index 32172be..a6bf86e 100644 --- a/artima/scheme/scheme19.ss +++ b/artima/scheme/scheme19.ss @@ -287,7 +287,8 @@ for the six episodes about the module system you are about to read. I was very ignorant about the module system when I started this project, and this work would not have been possible without the help of Abdulaziz Ghuloum, Derick Eddington, Will Clinger, Eli -Barzilay, Matthew Flatt and many others. Thank you guys, you rock! +Barzilay, Matthew Flatt, André van Tolder and many others. +Thank you guys, you rock! |# diff --git a/artima/scheme/scheme2.ss b/artima/scheme/scheme2.ss index 1535e59..a1e617e 100644 --- a/artima/scheme/scheme2.ss +++ b/artima/scheme/scheme2.ss @@ -31,13 +31,13 @@ very good interoperability with C, others well integrated in Java or in .NET, others with a particularly good documentation, others with especially useful libraries, but there is no single implementation with all the features which is definitively superior to the others. You may use -more than an implementation at the time, but you need to careful in +more than an implementation at the time, but you need to be careful in the choice of the libraries you are going to use, if you are interested in portability. I cannot say I have tried all Scheme implementations (there are dozens and dozens of them) so take my obvervations *cum grano salis*. I did -try `PLT Scheme`_, Bigloo_, Chicken_, Guile_, Ikarus_ e Larceny_ which +try `PLT Scheme`_, Bigloo_, Chicken_, Guile_, Ikarus_ and Larceny_ which are Open Source, multiplatform and free. Other major implementations are `Chez Scheme`_ (the interpreter, called Petit Scheme is free, the compiler is not) e and `MIT Scheme`_ (available with GPL licence) @@ -59,7 +59,7 @@ is the R5RS-compatible implementation I like most since it has a very practical attitude: it is written by people working in the industry and not in the academy. Chicken is a compiler from Scheme to C and it is extremely easy to -write *wrappers* for C/C++ libraries. Moreover, there already hundreds +write *wrappers* for C/C++ libraries. Moreover, there are already hundreds of interesting libraries available. They are called *eggs*, just as in Python, and they work more a less in the same way. However, it must be noticed that Chicken had eggs years before Python and more @@ -187,7 +187,7 @@ which is invaluable in Scheme programming. I make heavy usage of all these features. By the way, I see now that I have used the term REPL -(Read-Eval-Print-Loop) which may be is unknown to a few readers; REPL +(Read-Eval-Print-Loop) which may be unknown to a few readers; REPL is just the Lisp name for what is called the interactive interpreter or console in the Python world, the one with the ``>>>`` prompt. In Scheme the REPL is very @@ -228,7 +228,7 @@ concerns introspection and debugging capabilities. Tracebacks and error messages are not very informative. Sometimes, you cannot even get the number of the line where the error occurred; the reason is that Scheme code can be macro-generated and the notion of line number -may become foggy. On the other hand, I must say than in the five +may become foggy. On the other hand, I must say that in the five years I have being using Scheme (admittedly for toying and not for large projects) I have seen steady improvement in this area. @@ -248,7 +248,7 @@ management:: .. image:: plt-green.jpg -As you see, there is no much information: in particular the +As you see, there is not much information: in particular the information about the name of the function where the error occurred (``inv``) is lost and the line number/char number refers to the ``read-eval-print-loop`` code. You may contrast @@ -268,7 +268,7 @@ that with the Python traceback:: I should mention however that PLT is meant to be run inside its own IDE, DrScheme_. DrScheme highlights the line with the error and -include a debugger. However such functionalities are not that common +includes a debugger. However such functionalities are not that common in the Scheme world and in my own experience it is much more difficult to debug a Scheme program than a Python program. diff --git a/artima/scheme/scheme20.ss b/artima/scheme/scheme20.ss index fc544e7..d3b6e7c 100644 --- a/artima/scheme/scheme20.ss +++ b/artima/scheme/scheme20.ss @@ -287,29 +287,6 @@ and thus offer full autocompletion. ;;END ;(assert-distinct a b 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))))) - -;;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 ...) diff --git a/artima/scheme/scheme25.ss b/artima/scheme/scheme25.ss index e5465b1..f2cb01a 100644 --- a/artima/scheme/scheme25.ss +++ b/artima/scheme/scheme25.ss @@ -1,38 +1,103 @@ -#|Recursive macros +#|Macros, again ===================================================================== -After a short introduction about the relevance of macros for -programming language design, I show a few common patterns of Scheme -macrology: recursive macros, accumulators, and the usage of literals -to incorporate helpers in macros. - -Should everybody be designing her own programming language? +This is episode #25, the beginning of another block +of six posts constituting part V of my Adventures. +Part I was an introduction to the language; part II was +an introduction to macros; part III was an introduction to functional +programming and part IV an introduction to the module system. +We are done with the introductions now, and we are ready to face +more beefy matters. In particular part V main is concerned with +expert-level macrology. + +In the following six episodes I will +discuss various techniques and patterns which are commonly +used by Scheme macro writers, such as recursive macros, higher order +macros, and more. I will go more in detail +about how macros work, explaining what a syntax object is, what +a (non-)hygienic macro is and how you can to compare lexical +identifiers in macros. + +I will also discuss the relation +between my own ``sweet-macros`` and traditional macros (``syntax-rules``, +``syntax-case`` and ``define-macro``) so that you should be able to +program with any Scheme macrology out there. + +Finally I will give some nontrivial example of macro, such as a +macro implementing runtime pattern matching for lists, by filling the +gap left in episode 15_ . + +.. _homoiconicity: http://en.wikipedia.org/wiki/Homoiconicity +.. _15: http://www.artima.com/weblogs/viewpost.jsp?thread=249681 +.. _MetaPython: http://metapython.org/ +.. _Logix: http://www.livelogix.net +.. _Dylan: http://en.wikipedia.org/wiki/Dylan_programming_language + +Macros pros and contras ------------------------------------------------------------------ Macros are the reason why I first became interested in Scheme, five or six years ago. At the time - as at any time - there was a bunch of -trolling in comp.lang.python, and arguing for the addition of macros -to Python. Of course most Pythonistas opposed the idea, but -I had no idea of the advantages/disadvantages off the proposal; I felt -quite ignorant and powerless to argue. Since I never liked to feel ignorant, -I decided to learn macros, especially Scheme macros, because they are -the state of the art for the topics. +people trolling in comp.lang.python, arguing for the addition of macros +to the language. Of course most Pythonistas opposed the proposal. + +However, at the time I had no idea of the advantages/disadvantages of +the proposal and I felt quite ignorant and powerless to argue. Since I +never liked to feel ignorant, I decided to learn macros, especially +Scheme macros, because they are the state of the art for what concerns +the subject. Nowadays I can say that the addition of macros to Python would be -a bad idea knowing what I am talking about. Actually, I have already -stated in episode 12_ an even stronger opinion, i.e. that macros -are not a good fit for enterprise-oriented language. Of course, -I am *not* implying that every enterprise should adopt -only enterprise-oriented languages; it is a matter of fact -that various cutting edge enterprises are taking advantage of -non-conventional and/or research-oriented languages, but -I see them as exceptions to the general rule. - -My opinion against macros in (most) enterprise programming -does not mean that macros are worthless, and indeed +a bad idea knowing what I am talking about. I have two main objections, +one technical (and less important) and one political (the more important). + +The technical reason is that I do not believe in macros in languages +without S-expressions. There are plenty of examples of macros for +languages without S-expression - for instance Dylan_ in the Lisp +world, or Logix_ and MetaPython_ in the Python world, but none of +them ever convinced me. Scheme macros are much better because of +the homoiconicity_ of the language ("homoiconicity" is just a big word for +the *code is data* concept). + +I have already stated in episode 12_ my political objection, i.e. my +belief that macros are not a good fit for enterprise-oriented +languages. Of course, I am *not* implying that every enterprise should +adopt only enterprise-oriented languages; it is a matter of fact that +various cutting edge enterprises are taking advantage of +non-conventional and/or research-oriented languages, but I see them as +exceptions to the general rule. + +In my day to day work (I use Python exclusively there) +I had occasion to write both small declarative +languages and small command-oriented languages, but they were so simple +that I did not feel the need for Scheme macros. Actually, judging +from my past experience, I think extremely unlikely that I will +ever need something as sophisticated as Scheme macros in my +daily work. This is why my opinion is against macros in (most) +enterprise programming. + +Having said that, I do not think that macros are worthless, and actually I think they are extremely useful and important in another domain, i.e. in the domain of design and research about programming -languages. The major interest of Scheme macros for me is that they +languages. + +Of course Scheme is not the only language where you can experiment +with language design, it is just the best language for this kind of +tasks. + +A few months ago I have described +some experiment I did with the Python meta object protocol, in +order to change how the object system work, and replace +multiple inheritance with traits_. I am interested with this +kind of experiments, even if I will never use them in +production code, and I use Scheme in preference for such purposes. + +.. _traits: + +Writing your own programming language +---------------------------------------- + +The major interest of Scheme macros for me lies in the fact that they *enable every programmer to write her own programming language*. I think this is a valuable thing. Everybody who has got opinions about language design, or about how an object system should should work, or @@ -44,7 +109,7 @@ own programming language, and certainly not everybody should *distribute* its own personal language. Nevertheless, I think everybody can have opinions about language design and some experiment with macrology can help to put to test such opinions and to learn -many things. +something. The easiest approach is to start from a Domain Specific Language (DSL), which does not need to be a fully grown programming diff --git a/artima/scheme/scheme26.ss b/artima/scheme/scheme26.ss index b1090c1..14415eb 100644 --- a/artima/scheme/scheme26.ss +++ b/artima/scheme/scheme26.ss @@ -6,16 +6,129 @@ In this episode I will give a couple of examples of second order macros taking other macros as argument. Moreover I will bring an argument in favor of good old parenthesis. +Scheme as an unfinished language +----------------------------------------------------------------------- + +Python is an example of a *finished* language. With *finished*, I mean +that the language has not only a well defined and complete core, but also +a full toolbox of shortcuts and niceties to take care of common +cases and to make easier the life of the programmer. In other words +Python (but I could name Ruby or a few other languages here) is an +example of a language which is both easy to use and powerful. It +spoils the programmer, and this is the reason it is so much popular +nowadays. Of course, others will prefer Ruby, or Scala, or +something else, but the concept of finished language should be +clear. + +On the other hand, Scheme feels very much like an unfinished language +from somebody coming from the Python world. While there are many +practical reasons why Scheme it is the way it is and could not be +different, I am convinced that Scheme has been left unfinished also +*on purpose*: the language is incomplete, but it provides a built-in +mechanism to give the user the ability to finish the language +according to its preferences. Such a mechanism of course is the +mechanism of macros and one of the main use of macros is to fill +the deficiencies left by the standard. + +.. image:: bikeshed.jpg + :class: right + :width: 400 + +I think the explanation for the current situation in Scheme is more +historical and social than technical. On one side, a lot of people in +the Scheme world want Scheme to stay the way it is, i.e. a language +for language experimentations and research more than a language for +enterprise work. On the other side, the fact that there are so many +implementations of Scheme makes difficult/impossible to specify too +much: this the reason why there are no standard debugging tools for +Scheme, but only implementation-specific ones. + +Finally, there is the infamous `bikeshed effect`_ to take in account. +The bikeshed effect is typical of any project designed by a committee: +when it comes to proposing advanced functionalities that very few +can understand, it is easy to get approval from the larger community. +However, when it comes to simple functionality of common usage, everybody +has got a different opinion and it is practically impossible to get +anything approved at all. + +To avoid that, the standard does not provide +directly usable instruments: instead, it provides general instruments +which are intended as building blocks on that of which everybody can +write the usable abstractions he/she prefers. Most people nowadays +prefer to have ready-made solutions, because they have deadlines, +projects to complete and no time nor interest in writing things +that should be made by language designers, so that Scheme is little +used in the enterprise world. + +The difference between Scheme and its concrete implementations +-------------------------------------------------------------------- + +In the previous paragraph I have been a little unfair: while it is +true that Scheme - in the sense of the language specified by the +R6RS standard - is unfinished, concrete implementations of Scheme +tends to be much more complete. Consider for instance PLT Scheme, +or Chicken Scheme, which are two big Scheme implementations: they +have both decent size libraries and they are perfectly usable +(and used) for practical tasks you could do with Python or another +more mainstream language. Another option is to use a Scheme +implementation running on the Java virtual machine or on the .NET +platform. Alternatively, you could use a Scheme-like +language such as Clojure_. Clojure runs on the Java Virtual Machine, +it is half lisp and half Scheme, it has a strong functional flavour in +it, it has interesting things to say about concurrency_. It also +shares the following caracteristics with Python: + +1. it is a one-man language (i.e. it is not a comprimise language made + by a committee) with a clear philosophy and internal consistency; + +2. it is language made from scratch, with no preoccupations of backward + compatibility; + +3. it provides special syntax/libraries for common operations ( + `syntax conveniences`_) that would never enter in the Scheme standard. + +Such caracteristics make Clojure very appealing to me. The only +problems is that professionally I have no need to +interact with the Java platform (and even there I would probably +choose Jython over Clojure for reason of familiarity) so I have not +checked out Clojure and I have no idea about it except what you can +infer after reading its web site. If amongst my readers +there is somebody with experience in Clojure, please feel free to add +a comment to this post. + +My point here is that users that do not care at all for the freedom +to "finish" their language and prefer a BDFL language where +everything has been already finished and polished for them have +that option, by choosing Clojure. + +I personally am using Scheme since I am interested in macrology and no +language in existence can beat Scheme in this respect. Also, I am +using for Scheme for idle speculation and not to get anything done ;-) +A typical example of idle speculation is +the following question: would Scheme be a better language if it has +fewer parenthesis? + +.. _Clojure: http://clojure.org/ +.. _syntax conveniences: http://clojure.org/special_forms +.. _concurrency: http://clojure.org/concurrent_programming +.. _bikeshed effect: http://en.wikipedia.org/wiki/Bikeshed + + Two second order macros to reduce parenthesis ------------------------------------------------------------- -In the last episode I have defined a recursive ``cond-`` macro taking +While finding tricks for reducing parenthesis is pointless, +it gives me a reason to teach a few other macro programming +techniques: in particular, here I will discuss second order +macros taking macros as arguments. + +In the last episode I defined a recursive ``cond-`` macro taking less parenthesis than a regular ``cond``, using an accumulator. Here I will generalize that approach, by abstracting the accumulator functionality into a second order macro, called ``collecting-pairs``, which takes as input another macro and a sequence of arguments, and calls the input macro with the arguments grouped in pairs. -That make possible to call with less parenthesis any macro of +That makes it possible to call with less parenthesis any macro of the form ``(macro expr ... (a b) ...)``, by calling it as ``(collecting-pairs (macro expr ...) a b ...)``. @@ -71,7 +184,9 @@ for instance Paul Graham's Arc_ uses less parenthesis. This is possible since it does not provide a macro system based on pattern matching (which is a big *minus* in my opinion). Is it possible to have both? A syntax with few parenthesis for writing code manually -and a syntax with many parenthesis for writing macros. The answer is yes: +and a syntax with many parenthesis for writing macros? + +The answer is yes: the price to pay is to double the constructs of the language and to use a Python-like approach. @@ -102,9 +217,6 @@ in practice Scheme only provides the low level syntax, leaving to the final user the freedom (and the burden) of implementing his own preferred high level syntax. -Can a language be both easy and powerful? ------------------------------------------------------------------------ - When it comes to designing programming languages, easy of use and power seems to go in opposite directions. There are plenty of examples where something went wrong, i.e. simple languages which are @@ -113,90 +225,6 @@ professional languages which are too tricky to use for the casual programmer. We have also examples of languages which are both weak in power *and* difficult to use (insert your chosen language here). - -Nevertheless, I think it is perfectly possible to design a language -which is both easy to use and powerful. For instance, Python is a good -example of such language (others will prefer Ruby, or Scala, or -anything else they like). - -There are various reasons why Python can be both easy to use and powerful, -the most important ones being the following, in my opinion: - -1. it is a one-man language (i.e. it is not a comprimise language made by a - committee); - -2. it is language made from scratch, with no preoccupations of backward - compatibility; - -3. between (premature) optimization and easy of use Python always chooses - the latter; - -4. it provides special syntax/libraries for common operations. - -Scheme does not share any of these characters, and as a consequence it -is definitively not an easy language. It is just a powerful language. - -However, it is powerful enough that you can make it easy to use, but -that requires (a lot of work) on the part of the programmer, which must -implement point 4 by himself, whereas -nowadays we are all spoiled and we expect the language implementors to -do this kind of work for us. - -.. image:: bikeshed.jpg - :class: right - :width: 400 - -I think the explanation for the current situation in Scheme is more historical -and social than technical. On one side, a lot of people in the Scheme -world want Scheme to stay the way it is, i.e. a language for language -experimentations and research more than a language for enterprise -work. On the other side, the fact that there are so many -implementations of Scheme makes difficult/impossible to specify too -much: this the reason why there are no standard debugging tools for -Scheme, but only implementation-specific ones. - -Finally, there is the infamous `bikeshed effect`_ to take in account. -The bikeshed effect is typical of any project designed by a committee: -when it comes to proposing advanced functionalities that very few -can understand, it is easy to get approval from the larger community. -However, when it comes to simple functionality of common usage, everybody -has got a different opinion and it is practically impossible to get -anything approved at all. - -To avoid that, the standard does not provide -directly usable instruments: instead, it provides general instruments -which are intended as building blocks on that of which everybody can -write the usable abstractions he/she prefers. Most people nowadays -prefer to have ready-made solutions, because they have deadlines, -projects to complete and no time nor interest in writing things -that should be made by language designers, so that Scheme is little -used in the enterprise world. - -There are other options, however, if you are interested in a Scheme -for usage in the enterprise world. You can just use a Scheme -implementation running on the .NET or the Java platform, or a Scheme-like -language such as Clojure_. Clojure runs on the Java Virtual Machine, -it is half lisp and half Scheme, it has a strong functional flavour in -it, it has interesting things to say about concurrency_, -it is a one-man language (Rich Hickey is the BDFL) and provides -access to all the Java libraries. Moreover it provides a whole set -of `syntax conveniences`_ that would never enter in the Scheme standard. - -Professionally I have never -interacted with the Java platform (and even there I would probably -choose Jython over Clojure for reason of familiarity) so I have not -checked out Clojure and I have no idea about it except what you can -infer after reading its web site. If amongst my readers -there is somebody with experience in Clojure, please feel free to add -a comment to this post. - -I personally am using Scheme since I am interested in macrology and no -language in existence can beat Scheme in this respect. - -.. _Clojure: http://clojure.org/ -.. _syntax conveniences: http://clojure.org/special_forms -.. _concurrency: http://clojure.org/concurrent_programming -.. _bikeshed effect: http://en.wikipedia.org/wiki/Bikeshed |# (import (rnrs) (sweet-macros) (for (aps lang) run expand) diff --git a/artima/scheme/scheme27.ss b/artima/scheme/scheme27.ss index 2f1828e..edc0b6a 100644 --- a/artima/scheme/scheme27.ss +++ b/artima/scheme/scheme27.ss @@ -54,10 +54,10 @@ syntax object with the ``syntax->datum`` primitive:: > (equal? (syntax->datum syntax-expr) '(display "hello")) #t -Different syntax-objects can be equivalent: for instance -the improper list of syntax objects ``(cons #'display (cons #'"hello" #'()))`` -is equivalent to the syntax object ``#'(display "hello")`` in -the sense that both corresponds to the same datum:: +Different syntax-objects can be equivalent: for instance the improper +list of syntax objects ``(cons #'display (cons #'"hello" #'()))`` is +equivalent to the syntax object ``#'(display "hello")`` in the sense +that both corresponds to the same datum:: > (equal? (syntax->datum (cons #'display (cons #'"hello" #'()))) (syntax->datum #'(display "hello"))) @@ -109,7 +109,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 future episodes). +I will go back to that in the next episode). What ``syntax-match`` really is -------------------------------------------------------------- diff --git a/artima/scheme/macro4.ss b/artima/scheme/scheme28.ss index 66114a2..0ef3bb7 100644 --- a/artima/scheme/macro4.ss +++ b/artima/scheme/scheme28.ss @@ -1,4 +1,4 @@ -#|Relation with standard macros +#|Hygienic macros ============================================================== In episode 9_ I akwnoledged the fact that Scheme provides at least three @@ -28,15 +28,15 @@ 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``: in particular you cannot -break hygiene with it. Moreover, ``syntax-rules`` macros +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 follows:: +``syntax-case`` can also be defined in terms of ``syntax-match`` as +follows:: (def-syntax syntax-case (syntax-match () @@ -63,28 +63,22 @@ Why do I say so? ``syntax-match`` versus ``define-macro`` --------------------------------------------------------------- -Nowadays macros based on ``define-macro`` are much less used than in past, -in part because of the hygiene issue, and in part because macro systems based -on pattern matching are much more powerful and easy to use. The R6RS -specification made ``syntax-case`` enters in the standard and this is -the preferred macro system for most implementation. For instance -Chicken Scheme, which traditionally used ``define-macro`` a lot, is -going to remove it from the core, using ``syntax-case`` instead: this -is the reason why the next major Chicken version, Chicken 4.0, will be -called "hygienic Chicken". +Nowadays macros based on ``define-macro`` are much less used than in +past because macro systems based on pattern matching are much more +powerful, easier and safer to use. The R6RS specification made +``syntax-case`` enter in the standard and this is the preferred macro +system for most implementation. Nowadays, there is a good chance that your Scheme implementation does -not provide ``define-macro`` out of the box, therefore you need to implement -it in term of ``syntax-case`` (or ``syntax-match``). Here is an example -of such an implementation: +not provide ``define-macro`` out of the box, therefore you need to +implement it in term of ``syntax-case`` (or ``syntax-match``). Here is +an example of such an implementation: $$DEFINE-MACRO The code should be clear: the arguments of the macro are converted into a regular list which is then transformed with the expander, and converted -back into a syntax object in the context of the macro. The problem with -``define-macro`` (and the reason it is becoming less and less used in -the Scheme world) is the hygiene problem. +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 @@ -102,10 +96,22 @@ 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". + 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. +reference I know. Another extremely 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. .. image:: hygienic-paper-small.jpg @@ -130,20 +136,32 @@ the macro is not safe under variable capture and indeed code such as 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. +everything works fine if the ``for`` macro is defined using +``def-syntax``. There is no doubt that ``def-syntax`` is +nicer/simpler to writer than ``define-macro`` *and* much less error +prone. + +The other problem with non-hygienic macros is that introduced identifiers +will have the scope of expanded code, not the scope of the original macro: +that means that if the outer scope redefines the meaning of an +identifier used internally, the macro will work in an unexpected way. +Consider for instance the following expression: -Hygiene is regarded as a great virtue in the Scheme -community, less so in the Common Lisp community. +> (let ((unless 'unless)) + (dirty-for i 1 3 (print i))) + +There is an error here because shadowing ``unless`` affects the +``dirty-for`` macro. This is pretty tricky to debug: in practice, it +means that the macro user is forced to know all the identifiers +that are used internally by the macro. Breaking hygiene ------------------------------------------------- If you only use hygienic macros, the hygiene probleme does not exist. -However, there is the opposite problem: you neede a way of breaking hygiene on -purpose. Consider for instance the following apparently trivial -macro: +However, there is the opposite problem: you need a way of breaking +hygiene on purpose. Consider for instance the following apparently +trivial macro: .. code-block:: scheme @@ -179,7 +197,7 @@ introduced identifiers with some specific mechanism (it could be based on marking the names, or on explicit renaming). Once you get used to the idea that the expansion is not -literal, and that all the names internally defined by a macro +literal, and that all the identifiers internally used by a macro are opaque unless they are explicitly marked as visible, you will see the advantages of hygiene. @@ -187,8 +205,7 @@ For instance, if you are writing a library which can be imported in an unknown environment, in absence of hygiene you could introduce name clashes impossible to foresee in advance, and that could be solved only by the final user, which however will likely be ignorant of how -your library works. Therefore hygiene is a very good think, since it -frees your from wondering about name clashes. +your library works. To be fair, I should remark that in Common Lisp there @@ -201,12 +218,13 @@ new names in a macro is called *breaking hygiene*. Scheme provides a builtin mechanism to break hygiene, via the ``datum->syntax`` utility which converts literal objects (*datums*) into syntax objects. -I have first cited ``datum->syntax`` in episode 21 and you have seen -it at work in the definition of ``define-macro`` from ``syntax-match``: -here it is used to convert a list describing source code into a syntax -object which is then expanded. -The typical use case for ``datum->syntax`` is to turn symbols +I have already shown ``datum->syntax`` +at work in the definition of ``define-macro`` from ``syntax-match``, +where it was used to convert a list describing source code into a syntax +object. + +A typical use case for ``datum->syntax`` is to turn symbols into proper identifiers which can be introduced in macros and made visible to expanded code, thus breaking hygiene. Here is how you can "fix" the macro ``define-a``: @@ -223,6 +241,20 @@ the identifier ``a`` is really introduced as follows: > (define-a 1) > a 1 + +A more realistic example is to build identifiers from strings and +symbols. For that purpose I have added an ``identifier-append`` +utility in my ``(aps lang)`` library, defined as follow: + +$$lang:IDENTIFIER-APPEND + +Here is a simple ``def-book`` macro using ``identifier-append``: + +$$DEF-BOOK + +to be used as in the following test: + +$$TEST-DEF-BOOK |# (import (rnrs) (sweet-macros) (aps lang) (aps list-utils) (aps compat) @@ -232,10 +264,10 @@ the identifier ``a`` is really introduced as follows: (def-syntax define-macro (syntax-match () (sub (define-macro (name . params) body1 body2 ...) - #'(define-macro name (lambda params body1 body2 ...))) + #'(define-macro name (lambda params body1 body2 ...))) (sub (define-macro name expander) - #'(def-syntax (name . args) - (datum->syntax #'name (apply expander (syntax->datum #'args))))) + #'(def-syntax (name . args) + (datum->syntax #'name (apply expander (syntax->datum #'args))))) )) ;END @@ -249,6 +281,28 @@ the identifier ``a`` is really introduced as follows: `(define a ,x)) ;END +;;DEF-BOOK +(def-syntax (def-book name title author) + (: with-syntax + name-title (identifier-append #'name "-title") + name-author (identifier-append #'name "-author") + #'(begin + (define inner-name (vector title author)) + (define name-title (vector-ref inner-name 0)) + (define name-author (vector-ref inner-name 1))))) + +;;END + +;(pretty-print (syntax-expand (def-book bible "The Bible" "God"))) + + ;;TEST-DEF-BOOK + (test "def-book" + (let () + (def-book bible "The Bible" "God") + (list bible-title bible-author)) + (list "The Bible" "God")) + ;;END + (def-syntax (let-3 name list-3 body body* ...) #`(let+ ((x y z) list-3) (let ((#,(identifier-append #'name ".x") x) diff --git a/artima/scheme/macro5.ss b/artima/scheme/scheme29.ss index 18ca87c..82872b0 100644 --- a/artima/scheme/macro5.ss +++ b/artima/scheme/scheme29.ss @@ -1,5 +1,5 @@ #| -Generating temporary identifiers +Records from macros ========================================================================= In this episode I show how to introduce auxiliary identifiers in a @@ -13,10 +13,10 @@ record types and I discuss the hygienic feature of Scheme macros. 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. +(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. Here is an example where the temporary variable is used as argument in the lambda function: a ``fold`` macro @@ -137,32 +137,27 @@ 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 ---------------------------------------------------------------------- +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*. - -.. image:: hygienic-paper-small.jpg +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; @@ -183,14 +178,6 @@ 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. - -In general, if you are writing a library which can be imported -in an unknown environment, in absence of hygiene you could introduce -name clashes impossible to foresee in advance, and that could be solved -only by the final user, which however will likely be ignorant of how -your library works. Therefore hygiene is a very good think, since it -frees your from wondering about name clashes. - |# (import (rnrs) (sweet-macros) (for (aps lang) run expand) diff --git a/artima/scheme/scheme3.ss b/artima/scheme/scheme3.ss index 5df7765..b77cc57 100644 --- a/artima/scheme/scheme3.ss +++ b/artima/scheme/scheme3.ss @@ -1,5 +1,5 @@ #| -Of parenthesis and indentation +Of parentheses and indentation ================================================================= In the previous two episodes I have discussed a few important subjects @@ -7,10 +7,10 @@ such as availability of libraries, reliability of implementations, support in case of bugs, etc. However, I have not said anything about the language *per se*. In this episode I will talk more about the language, by starting from the syntax, with a discussion of the infamous -parenthesis. Lisp parens have been the source of infinite debates +parentheses. Lisp parens have been the source of infinite debates from the very beginning and always will be. As you probably know, -LISP_ means *Lots of Irritating Superfluous Parenthesis*, and Scheme -has even more parenthesis than other Lisps! +LISP_ means *Lots of Irritating Superfluous Parentheses*, and Scheme +has even more parentheses than other Lisps! .. _LISP: http://www.acronymfinder.com/af-query.asp?acronym=LISP @@ -24,7 +24,7 @@ I came back to Emacs, I asked on comp.lang.scheme_ and comp.emacs_ and I was pointed out to excellent *scheme mode*, called quack.el_ and written by Neil Van Dike. Moreover, I have discovered how to augment the contrast of the parens -(parens-edit mode) and I feel completely confortable. But let me +(parens-edit mode) and I feel completely comfortable. But let me repeat that it is suicidal to try to edit Scheme/Lisp code without a good support from the editor. In my opinion this is the first reason why legions of beginners @@ -48,7 +48,7 @@ programming language is a good think. In theory, everybody should have the freedom to choose her editor, and it should be possible to program even in Notepad (even if it is a thing I do not wish to anybody!). This in theory: in practice, every -professional programmer use some dedicate tool to write code, +professional programmer use some dedicated tool to write code, so if you don't want to use Emacs please make sure your editor/IDE has a good Scheme mode, otherwise consider changing your developing environment. @@ -62,8 +62,8 @@ environment. It is interesting to notice what `Paul Graham`_ - a big name in the Lisp community and the main -of Arc_, a new language of the Lisp family recently released -and implemented in PLT Scheme - says about the parenthesis: +author of Arc_, a new language of the Lisp family recently released +and implemented in PLT Scheme - says about the parentheses: *We also plan to let programmers omit parentheses where no ambiguity would result, and show structure by indentation instead of parentheses. @@ -72,26 +72,26 @@ and implemented in PLT Scheme - says about the parenthesis: .. image:: paulgraham.jpg -Arc_ for the moment seems to require the parens, but is has -a bit less parenthesis than Scheme, as you can infer from +Arc_ for the moment seems to require the parens, but it has +a bit less parentheses than Scheme, as you can infer from the tutorial_. It is clear that the parens are NOT necessary and one could imagine -various tricks to reduce their number (I myself I tried various +various tricks to reduce their number (I personally tried various approaches when I began programming in Scheme). There is also an SRFI (SRFI-49_: *Indentation-sensitive syntax*) -that proposes to use indentation instead of parenthesis keeping +that proposes to use indentation instead of parentheses taking inspiration from Python (!) The proposal should be considered as curiosity; discussing about indentation could have had some sense thirty years ago, at the time Scheme was designed. Nowadays, when -100% of Scheme code is written with parenthesis, there is no point -in not using them. The beginners would be penalized if she started +100% of Scheme code is written with parentheses, there is no point +in not using them. Beginners would be penalized if they started using a style nobody uses. In my (semi-serious) opinion, parens are a real *initiation test*: if a programmer cannot stand them than he is not worth of using Scheme/Lisp and he should address himself on inferior languages -(i.e. *all* languages, according to wht the majority of +(i.e. *all* languages, according to what the majority of Schemers/Lispers think). In my experience the snobish attitude is more common in the Common Lisp community whereas in the Scheme community there is more respect for the newbie. @@ -105,7 +105,7 @@ in other languages, since only the most persistent survive. As a Pythonista I do not believe in those tricks: I think every language should be made accessible to the largest possible public. -That means that many second rate programmers will be able to learn +That means many second rate programmers will be able to learn it, but this is an opportunity, not an issue: the existence of poor programmers increases the number of available positions, since you need people to fix their mistakes! Otherwise how would you justify @@ -114,20 +114,20 @@ the number of job offers for Java and C++? Anyway, when after long suffering one has learned to manage with parens, there is no way back: once you have mastered a good editor the -parens gives you strong advantages when writing code. One of the main -ones is automatic indentation with a single keypress. Never more +parens give you strong advantages when writing code. One of the main +ones is automatic indentation with a single keypress. No more snippets of code send via e-mail and made unredable by the newlines -added by the mail program; never more time wasted on reindenting code +added by the mail program; no more time wasted on reindenting code when refactoring. -Of course, nothing is perfect, and you may forget a parens here and +Of course, nothing is perfect, and you may forget a paren here and there, but overall I will definitively admit that in the long run the -parenthesis pay off. On the other hand, on the short run, they make +parentheses pay off. On the other hand, in the short run, they make life much harder for the newbies; I still think that an optional -syntax with less parenthesis to be used by beginners or when using a +syntax with less parentheses to be used by beginners or when using a poor editor would make sense, in a new Scheme-like langauge. But it is too late for Scheme itself: for the best or for the worst Scheme is -a language full of parenthesis and it is best to take full advantage +a language full of parentheses and it is better to take full advantage of them. *Nota Bene*: new languages based on *s*-expressions are born every day @@ -135,7 +135,7 @@ of them. which runs on the Java platform and is very interesting). For those new languages it may have sense to investigate the available options. The best reference -discussing alternative to parenthesis that I know of is `a paper +discussing alternative to parentheses that I know of is `a paper by David Wheeler`_. It is an interesting reading, you may want to have a look at it, if you are interested in the topic. @@ -153,7 +153,7 @@ write ``(+ 1 1)``. The sum operator ``+`` is written at the beginning, as a prefix, and not in the middle, as an infix. I never had any trouble with infix syntax (I had trouble with parens instead) -since it is something perfectly consistente: in Python +since it is something perfectly consistent: in Python the function name is written before the arguments too. Actually, when you write ``1+1`` in Python, you should think of @@ -180,14 +180,14 @@ famous *batteries included* concept), whereas Scheme does not care. Probably the real issue is that it is impossible to get consensus in the committee about the size of the standard library and about what to include in it, but the final result is -that user is stack with a very small standard library. Anyway, I +that user is stuck with a very small standard library. Anyway, I should say that the standard library was much smaller before the R6RS, so the situation is improving. -Moreover, concrete implementation often have a lot of useful +Moreover, concrete implementations often have a lot of useful (but non-portable) libraries. -But let me come back to syntax. It must be noticed that the prefix syntax -has enourmous advantages when macros enter in the game. The absolute +But let me go back to syntax. It must be noted that the prefix syntax +has enourmous advantages when macros enter the game. The absolute regularity of Scheme/Lisp programs, which are sequences of *s*-espressions of the form @@ -198,15 +198,15 @@ automatic generation of code extremely effective. I will discuss this point in detail in future episodes; here I can anticipate that *Scheme code is not meant to be written by humans, it is intended to be written automatically by macros*. Only after having -understood this point you will realize that the parenthesis are +understood this point you will realize that the parentheses are a Good Thing (TM). I needed a few months to understand it, others never understand it and they quit Scheme with disgust. If you will be able to pass the initiation test you will see that *s*-expressions (which are usually but not *necessarily* -associated to parenthesis) make sense. Once understood it, +associated to parentheses) make sense. Once understood, the traditional (infix) notation becomes an obstacle more than -an help. Moreover the total uniformity of Scheme programs has +a help. Moreover the total uniformity of Scheme programs has a kind of beauty and elegance in itself. No useless syntax, no boilerplate code, you breath an air of Zen minimalism. |# diff --git a/artima/scheme/scheme5.ss b/artima/scheme/scheme5.ss index 3aef13c..9f44bce 100644 --- a/artima/scheme/scheme5.ss +++ b/artima/scheme/scheme5.ss @@ -11,8 +11,8 @@ times the same instructions and measuring the total time spent? We will see in a moment that there is an easy solution to this question (use recursion). On the other hand, there is a second more serious question: since there is no fully portable way to write a library in -Scheme, how can we write a benchmark library? There is no real answer -to this question, so we will restrict ourselves to R6RS-compliant +Scheme, how can we write a benchmark library? There is no real answer, +so we will restrict ourselves to R6RS-compliant Scheme implementations where there is a standard concept of library. There are no ``for`` loops in Scheme @@ -20,10 +20,10 @@ There are no ``for`` loops in Scheme The ``for`` loop is missing in Scheme as a primitive construct since it is useless in a language that guarantees tail call optimization. -If you studied the concept of *tail call* at the college you know what +If you studied the concept of *tail call* at college you know what I am talking about; on the other hand, if you forgot it, or if you did -study Physics like myself, it is worth spenting a couple of words on -the subject. The ones who wants to know more, may consult +study Physics like me, it is worth spending a couple of words on +the subject. The ones who want to know more, may consult this `Wikipedia article`_. .. image:: Ouroboros.png @@ -38,7 +38,7 @@ the Python loop:: for i in range(1,4): print i, -can be converted as a recursive function ``print_up_to_3``:: +can be converted to a recursive function ``print_up_to_3``:: def print_up_to_3(i): if i == 4: return @@ -50,7 +50,7 @@ can be converted as a recursive function ``print_up_to_3``:: Here the last instruction of the function (the tail) is a call to itself, hence the name *tail call*. -The tail call optimization is an optimization guaranteed by the +Tail call optimization is guaranteed by the Scheme language. Scheme compilers/interpreters are able to recognize recursive functions in tail call form and to convert them internally in ``for`` loops. As a consequence, the programmer @@ -83,13 +83,13 @@ Let me point out two things before closing this paragraph: > (let ((a 1) (b 2)) (+ a b)) ; => 3 - The scope of ``a`` and ``b`` is limited to the current block; - if ``a`` and ``b`` are defined outside the ``let`` block, internally + The scope of ``a`` and ``b`` is limited to the current S-expression/form; + if ``a`` and ``b`` are defined outside the ``let`` form, internally ``a`` and ``b`` *shadow* the outer names. 2) there is actually a ``do`` loop in the language, but it is cumbersome to use and redundant because the *named let* allows you to perform - anything ``do`` does. I see it as an useless construct in a language + everything ``do`` does. I see it as a useless construct in a language that would like to be minimalist but it is not. .. _Wikipedia article: http://en.wikipedia.org/wiki/Tail_call_optimization @@ -97,11 +97,11 @@ Let me point out two things before closing this paragraph: There is no portable module system --------------------------------------------------------------------- -As I have anticipated before, libraries are the weak point of Scheme. +As I have anticipated, libraries are the weak point of Scheme. There are few libraries available and it is also difficult to -write portable libraries. The reason is that historically Scheme +write portable ones. The reason is that historically Scheme never had any standard module system until very recently, with -the R6RS document: that means that nearly all current implementations +the R6RS document: that means nearly all current implementations provide different and incompatible module systems. In order to understand the reason for this serious lack, you must @@ -110,16 +110,16 @@ understand the philosophy behind Scheme, i.e. the so called For thirty years the Scheme community has not been able to converge on a well done single module system. It is only in 2007 that a standard module system has been blessed by the Scheme committee: but even that -was done was a lot of opposition and there are implementors who -said they will *never* support R6R5. +was done with a lot of opposition and there are implementors who +said they will *never* support R6RS. -As a consequence of history and mentality, even as pof now, if you +As a consequence of history and mentality, if you want to write a library for implementation X, you need to do a lot of boring and uninspiring work to port the library to implementations Y, Z, W, ... (there are *dozens* of different implementations). Moreover, a few implementations do not have a module system at all, so you may be forced to solve name clashes issue *by hand*, changing the -names of the functions exported by our library, if they shadow names +names of the functions exported by your library, if they shadow names coming from third party libraries (!) Personally, I picked up Scheme 5 years ago, but never used it @@ -243,7 +243,7 @@ with the following R6RS-compliant script (written in Ikarus Scheme):: I will notice two things: -1. Python manage to compute the factorial of 995, but then it faces +1. Python manages to compute the factorial of 995, but then it faces the stack wall and it raises a ``RuntimeError: maximum recursion depth exceeded`` whereas Scheme has no issues whatsoever; @@ -260,16 +260,16 @@ long integers is more efficient than the Python one, but they say nothing on the relative performances of the two languages. It is more interesting to see what happens for small numbers. For instance, in order to compute the factorial of 7 for 10 millions -of times, Python takes 30.5 seconds, whereas Ikarus taks +of times, Python takes 30.5 seconds, whereas Ikarus takes 3.08 seconds and thus it is nearly *ten times* faster than Python. This is not surprising at all, since function calls in Python are especially slow whereas they are optimized in Scheme. Moreover Ikarus is a native code compiler. -That means that Ikarus' REPL_ works by compiling expressions to native code, +It means Ikarus' REPL_ works by compiling expressions to native code, whereas Python's REPL compiles to bytecode. The technology is called incremental compilation and it is commonly used in Lisp languages -from decades, even it may look futuristic for C/C++ programmers. +from decades, even if it may look futuristic for C/C++ programmers. The factorial example is not very practical (on purpose), but it is significant, in the sense that it is legitimate to expect good performances from Scheme compilers. The fastest diff --git a/artima/scheme/scheme7.ss b/artima/scheme/scheme7.ss index dfa94b4..4eee28c 100644 --- a/artima/scheme/scheme7.ss +++ b/artima/scheme/scheme7.ss @@ -15,8 +15,8 @@ languages (with the exception of Ruby): the *symbol*. .. image:: symbols.jpg :height: 250 -From the grammar point of view, a symbols is just a quoted identifier, -i.e. a sequences of characters corresponding to a valid identifier +From the grammar point of view, a symbol is just a quoted identifier, +i.e. a sequence of characters corresponding to a valid identifier preceded by a quote. For instance, ``'a``, ``'b1`` e ``'c_`` are symbols. On the set of symbols there is an equality operator ``eq?`` which is able to determine if two symbols are the same or not:: @@ -28,7 +28,7 @@ which is able to determine if two symbols are the same or not:: #t ``#f`` e ``#t`` are the Boolean values False and True respectively, -how you may have imagined. The equality operator is extremely +as you may have imagined. The equality operator is extremely efficient on symbols, since the compiler associates to every symbol an integer number (this operation is called *hashing*) and stores it in an interal registry @@ -59,7 +59,7 @@ functions hash_ e intern_, which says: *normally, the names used in Python programs are automatically interned, and the dictionaries used to hold module, class or instance attributes have interned keys*. BTW, if you want to know exactly how string comparison works in Python -I suggest you to look at `this post`: +I suggest you to look at `this post`_: .. _hash: http://docs.python.org/lib/built-in-funcs.html#l2h-36 .. _intern: http://docs.python.org/lib/non-essential-built-in-funcs.html#l2h-90 @@ -67,7 +67,7 @@ I suggest you to look at `this post`: Scheme has much more valid identifiers than Python or C, where the valid characters are restricted to ``a-zA-Z-0-9_`` (I am ignoring the possibility of having Unicode characters in identifiers, which is -possible both in R6RS Scheme and Python 3.0). By conventions, symbols +possible both in R6RS Scheme and Python 3.0). By convention, symbols ending by ``?`` are associated to boolean values or to boolean-valued functions, whereas symbols ending by ``!`` are associated to functions or macros with side effects. @@ -82,7 +82,7 @@ sometimes:: #f The reason is that ``eq?`` (corrisponding to ``is`` in Python) checks -if two objects are the same objects at the pointer level, but it does +if two objects are the same object at the pointer level, but it does not check the content. Actually, Python works the same. It is only by accident than ``"pippo" is "pippo"`` returns True on my machine, since the CPython implementation @@ -110,7 +110,7 @@ and for integer numbers ``=``:: #t To be entirely accurate, in addition to ``eq`` and ``equal``, Scheme -also provides a third equality operator ``eqv``. ``eqv`` looks to me +also provides a third equality operator ``eqv?``. ``eqv?`` looks to me like an useless complication of the language, so I will not discuss it. If you are interested, you can read what the R6RS document `says about it`_. @@ -215,7 +215,7 @@ functions with different behavior according to the number of arguments. ``for-all`` is an R6RS higher order function: ``(for-all pred lst)`` applies the predicate ``pred`` to the elements of list ``lst``, until a false value is found - in that case it returns ``#f`` - -otherwise is returns ``#t``. Here the assertion checks at +otherwise it returns ``#t``. Here the assertion checks at runtime that all the passed arguments ``n0``, ``n`` and ``s`` are numbers, with ``s`` non-zero. @@ -253,7 +253,7 @@ I suggest you try the following exercises: I will show the solutions in the next episode. If you are lazy and you want an already written list library, I suggest you to give a look at the -SRFI-1_, libraries, which is very rich and available practically in all +SRFI-1_ library, which is very rich and available practically in all Scheme implementations. Many of the SRFI-1_ features are built-in in the R6RS standard, but many other are still available only in the SRFI-1_ . diff --git a/artima/scheme/scheme8.ss b/artima/scheme/scheme8.ss index 750d7ba..028d302 100644 --- a/artima/scheme/scheme8.ss +++ b/artima/scheme/scheme8.ss @@ -5,7 +5,7 @@ Quoting and quasi-quoting In this episode I will explain the meaning of the "code is data" concept. To this aim I discuss the quoting operation which allows to convert a code fragment -into a list of symbols and primitive values - i.e. converts code into +into a list of symbols and primitive values - i.e. it converts code into data. Then, I discuss the issue of evaluating data as code. Quoting @@ -13,10 +13,10 @@ Quoting A distinguishing feature of the Lisp family of languages is the existence of a quoting operator denoted with a quote ``'`` or with ``(quote )``, -the first form being syntacting sugar for the second. +the first form being syntactic sugar for the second. The quoting operator works as follows: -1. on primitive values such as numbers, literal strings, symbols etc, it +1. on primitive values such as numbers, literal strings, symbols, etc, it works as an identity operator: :: @@ -62,7 +62,7 @@ a dynamic library, then you can import it at runtime; you also have the mechanism of pre-processor macros at your disposal for working at compile time. The point is that there is no language where code generation is as convenient as in Scheme/Lisp where it is buil-in, -thanks to*s*-expressions or, you wish, thanks to parenthesis. +thanks to *s*-expressions or, you wish, thanks to parenthesis. Quasi-quoting ------------------------------------------------- @@ -92,12 +92,12 @@ our case we are unquoting the user name, ``,user``. The function containing the string ``"hello"`` together with the username. There is another operator like ``unquote``, called -``unquote-splice`` or comma-at and written ``,@``, which works as follows:: +``unquote-splicing`` or comma-at and written ``,@``, which works as follows:: > (let ((ls '(a b c))) `(func ,@ls)) (func a b c) -In practice ``,@ls`` "unpack" the list ``ls`` into its components: without +In practice ``,@ls`` "unpacks" the list ``ls`` into its components: without the splice operator we would get:: > (let ((ls '(a b c))) `(func ,ls)) @@ -108,7 +108,7 @@ since Scheme/Lisp code is nothing else than a list, it is easy to build code by interpolating a list template. For instance, suppose we want to evaluate a Scheme expression in a given context, where the contexts is given as a list of -binding, i.e. a list names/values:: +bindings, i.e. a list names/values:: (eval-with-context '((a 1)(b 2) (c 3)) '(* a (+ b c))) @@ -124,7 +124,7 @@ known by the interpreter; in our case we declared that ``eval`` understands all the procedures and macros of the most recent RnRS standard (i.e. the R6RS standard). The environment specification has the same syntax of an ``import``, since in practice it is the same concept: it is -possible to specify user-define modules as the ``eval`` environment. +possible to specify user-defined modules as the ``eval`` environment. This is especially useful if you have security concerns, since you can run untrusted code in a stricter and safer subset of R6RS Scheme. @@ -142,7 +142,7 @@ Programs writing programs :class: right Once you realize that code is nothing else than data, it becomes easy -to write programs taking in input source code and generating in output +to write programs taking as input source code and generating as output source code, i.e. it is easy to write a compiler. For instance, suppose we want to convert the following code fragment @@ -200,7 +200,7 @@ recursion) and structures more general than ``begin``: but I leave that as an useful exercise. In a future episode I will talk of *code-walkers* and I will discuss how to convert generic source code. In general, one can convert s-expression based source code by using -an external compiler, as we did here, or by using the buil-in mechanism +an external compiler, as we did here, or by using the built-in mechanism provided by Scheme macros. Scheme macros are particularly powerful, since they feature extremely advanced pattern matching capabilities: the example I have just given, based on the primitive list operations diff --git a/artima/scheme/table-of-contents.txt b/artima/scheme/table-of-contents.txt index 55f2355..b3e57be 100644 --- a/artima/scheme/table-of-contents.txt +++ b/artima/scheme/table-of-contents.txt @@ -78,4 +78,4 @@ Part IV - The Module System .. _Ep21: http://www.artima.com/weblogs/viewpost.jsp?thread=255612 .. _Ep22: http://www.artima.com/weblogs/viewpost.jsp?thread=256848 .. _Ep23: http://www.artima.com/weblogs/viewpost.jsp?thread=257998 -.. _Ep23: http://www.artima.com/weblogs/viewpost.jsp?thread=258103 +.. _Ep24: http://www.artima.com/weblogs/viewpost.jsp?thread=258103 |