diff options
Diffstat (limited to 'artima')
-rw-r--r-- | artima/scheme/Makefile | 2 | ||||
-rw-r--r-- | artima/scheme/scarti.txt | 37 | ||||
-rw-r--r-- | artima/scheme/scheme19.ss | 22 | ||||
-rw-r--r-- | artima/scheme/scheme20.ss | 158 | ||||
-rw-r--r-- | artima/scheme/scheme21.ss | 155 | ||||
-rw-r--r-- | artima/scheme/scheme23.ss | 229 | ||||
-rw-r--r-- | artima/scheme/scheme27.ss | 180 | ||||
-rw-r--r-- | artima/scheme/table-of-contents.txt | 2 | ||||
-rw-r--r-- | artima/scheme/use-ct-mapping.ikarus.ss | 8 | ||||
-rw-r--r-- | artima/scheme/use-ct-mapping.ss (renamed from artima/scheme/use-ct-mapping.mzscheme.ss) | 0 | ||||
-rw-r--r-- | artima/scheme/use-registry.ikarus.ss | 9 | ||||
-rw-r--r-- | artima/scheme/use-registry.mzscheme.ss | 12 | ||||
-rw-r--r-- | artima/scheme/use-registry.ss | 13 |
13 files changed, 451 insertions, 376 deletions
diff --git a/artima/scheme/Makefile b/artima/scheme/Makefile index e88904b..e906a05 100644 --- a/artima/scheme/Makefile +++ b/artima/scheme/Makefile @@ -150,7 +150,7 @@ fourth-cycle: fourth-cycle.txt $(RST) scheme20.ss; $(POST) scheme20.rst 255303 21: scheme21.ss - $(RST) scheme21.ss; $(POST) scheme21.rst + $(RST) scheme21.ss; $(POST) scheme21.rst 255612 22: scheme22.ss $(RST) scheme22.ss; $(POST) scheme22.rst diff --git a/artima/scheme/scarti.txt b/artima/scheme/scarti.txt index 454bd3a..a6c8bc1 100644 --- a/artima/scheme/scarti.txt +++ b/artima/scheme/scarti.txt @@ -583,3 +583,40 @@ Suppose you wanted to define the macros #'(def-syntax kw (let ((a (alist (name value) ...))) <more code here> ...))) + +Using list-comprehension +------------------------------------------------------------ + +For instance, once I needed to write the permutation of a set +of symbols in order to write a complex macro, and I did spend +a lot of time trying to implement them in time of ``map`` +and ``filter``, whereas it was trivial to solve the problem +with list comprehensions. +The algorithm is very clear; an empty list has no permutations:: + + > (perm '()) + () + +A list of lenght 1 has only one possible permutation:: + + > (perm '(a)) + ((a)) + +The permutations of list of length n are the sum of the +permutations of its sublists of lenght n-1:: + + > (perm '(a b));; the sublists are '(b) and '(a) + ((a b) (b a)) + > (perm '(a b c));; the sublists are '(b c), '(a c) and '(a b) + ((a b c) (a c b) (b a c) (b c a) (c a b) (c b a)) + +Here is the implementation using list comprehension:: + ;; compute the permutations of a list of distinct elements + (define (perm lst) + (cond + ((null? lst) '()); empty list + ((null? (cdr lst)) (list lst)); single element list + (else; multi-element list + (list-of (cons el subls) + (el in lst) + (subls in (perm (list-of e (e in lst) (not (eq? e el))))))))) diff --git a/artima/scheme/scheme19.ss b/artima/scheme/scheme19.ss index 687f0c9..398d2c0 100644 --- a/artima/scheme/scheme19.ss +++ b/artima/scheme/scheme19.ss @@ -1,10 +1,6 @@ -#| -The R6RS module system +#|The R6RS module system ========================================================= -Preamble ---------------------------------------------------------- - For nearly 30 years Scheme lived without a standard module system. The consequences of this omission were the proliferation of dozens of incompatible module systems and neverending debates. @@ -47,7 +43,7 @@ the ``lib.pyc`` file becomes outdated: the Python interpreter is smart enough to recognize the issue and to seamlessly recompile ``lib.pyc``. In Scheme the compilation process is very much *implementation-dependent*. -Here I will focus on the Ikarus mechanism, which is the most Pythonic one. +Here I will focus on the Ikarus mechanism, which is Pythonic enough. Ikarus has two modes of operation; by default it just compiles everything from scratch, without using any intermediate file. This is possible since the Ikarus compiler is very fast. However, @@ -74,6 +70,18 @@ lot of Python programs are actually calling underlying C libraries, so that Python can be pretty fast in some cases (for instance in numeric computations using numpy). +All I said for Ikarus, can be said from Ypsilon, with minor differences. +Ypsilon compiles to intermediate code, like Python. +Precompiled files are automatically generated +without the need to specify any flag, as in Python; however they +are stored in a so called auto-compile-cache directory, which by +default is situated in ``$HOME/.ypsilon``. Rhe location can +be changed by setting the environment variable ``YPSILON_ACC`` +or by passing the ``--acc=dir`` argument to the Ypsilon interpreter. +It is possible to disable the cache and to clear the cache; if you +are curious about the details you should look at Ypsilon manual +(``man ypsilon``). + Modules are not first class objects ------------------------------------------------------------- @@ -277,6 +285,6 @@ architecture of the target processor. .. image:: compiler-crosscompiler.jpg -.. _cross compilation: http://chicken.wiki.br/cross-compilation +.. cross compilation: http://chicken.wiki.br/cross-compilation .. _cross compilation: http://en.wikipedia.org/wiki/Cross_compilation |# diff --git a/artima/scheme/scheme20.ss b/artima/scheme/scheme20.ss index 04db2f7..f927dff 100644 --- a/artima/scheme/scheme20.ss +++ b/artima/scheme/scheme20.ss @@ -81,6 +81,8 @@ and thus very much implementation-dependent, I will focus on the compiler semantics of Scheme programs. Such semantics is quite tricky, especially when macros enters in the game. +.. _9: http://www.artima.com/weblogs/viewpost.jsp?thread=240804 + Macros and helper functions --------------------------------------------------------------------- @@ -91,9 +93,10 @@ simple macro $$ASSERT-DISTINCT which raises a compile-time exception (syntax-violation) if it is -invoked with duplicate arguments. A typical use case for such macro -is definining specialized lambda forms. The -macro relies on the builtin function +invoked with duplicate arguments. Such macro could be used as a +helper in macros defining multiple names at the same time, like +the ``multi-define`` macro of episode 9_. +``assert-distinct`` relies on the builtin function ``free-identifier=?`` which returns true when two identifiers are equal and false otherwise (this is a simplified explanation, let me refer to the `R6RS document`_ for the gory details) and @@ -101,18 +104,19 @@ on the helper function ``distinct?`` defined as follows: $$list-utils:DISTINCT? -Here are a couple of test cases for ``distinct?``: +``distinct?`` takes a list of objects and finds out they are all +distinct according to some equality operator, of if there are duplicates. +Here are a couple of test cases: $$TEST-DISTINCT -The problem with the evaluation semantics is that it is natural, when -writing the code, to define first the function and then the macro, and -to try things at the REPL. Here everything works; in some -Scheme implementation, like Ypsilon, this will also work as a -script, unless the strict R6RS-compatibility flag is set. -However, in R6RS-conforming implementations, if you cut and paste -from the REPL and convert it into a script, you will run into -an error! +It is natural, when writing new code, to try things at the REPL and to +define first the function and then the macro. The problem is that +everything works in the REPL; moreover, in some Scheme implementation +like Ypsilon, the code will also +work as a script (unless the strict R6RS-compatibility flag is set). +However, in R6RS-conforming implementations, if you cut and paste from +the REPL and convert it into a script, you will run into an error! The problem is due to the fact than in the compiler semantics macro definitions and function definitions happens at *different times*. In @@ -134,12 +138,18 @@ available at expand time a function defined at runtime is to define the function in a different module and to import it at expand time* +The `expansion process`_ of Scheme source code is specified in +the R6RS document. The standard has the generic concept of +*macro expansion time* which is valid even for interpreted +implementation when there is no compilation time. + Phase separation -------------------------------------------------------------- -I have put ``distinct?`` in the ``(aps list-utils)`` +Let me go back to the example of the ``assert-distinct`` macro. +I have put the ``distinct?`` helper function in the ``(aps list-utils)`` module, so that you can import it. This is enough to solve the -problem for Ikarus, which has no concept of *phase separation*, but it is not +problem of compile time vs runtime separation for Ikarus, but it is not enough for PLT Scheme or Larceny, which have full *phase separation*. In other words, in Ikarus (but also Ypsilon, IronScheme and Mosh) the following script @@ -154,13 +164,14 @@ is correct, but in PLT Scheme and Larceny it raises an error:: .. image:: salvador-dali-clock.jpg -The problem is that PLT Scheme has *strong phase separation*: by default +The problem is that PLT Scheme has *full phase separation* and therefore +requires *phase specification*: by default names defined in external modules are imported *only* at runtime, *not* at compile time. In some sense this is absurd since names defined in an external pre-compiled modules are of course known at compile time (this is why Ikarus has no trouble to import them at compile time); -nevertheless PLT Scheme and Larceny Scheme forces you to specify at +nevertheless PLT Scheme (and Larceny) forces you to specify at which phase the functions must be imported. Notice that personally I do not like the PLT and Larceny semantics since it makes things more complicated, and that I prefer the Ikarus semantics: @@ -180,102 +191,41 @@ With this import form, the script is portable in all R6RS implementations. Discussion ------------------------------------------------- -Is phase separation a good thing? -In my opinion, from the programmer's point of view, the simplest thing -is lack of complete lack of phase separation, and interpreter semantics, in -which everything happens at runtime. -If you look at it with honesty, at the end the compiler semantics is +Personally, I find the interpreter semantics the most intuitive and +easier to understand. In such semantics everything happens at runtime, +and there is no phase separation at all; it is true that the code may +still be compiled before being executed, as it happens in Ikarus, but +this is an implementation detail: from the point of view of the +programmer the feeling is the same as using an interpreter. +The interpreter semantics is also the most powerful semantics at all: +for instance, it is possible to redefine identifiers and it is +possible to import modules at runtime, things which are both impossible +in compiler semantics. + +After all, if you look at it with honesty, the compiler semantics is nothing else that a *performance hack*: by separing compilation time -from runtime you can perform some computation only once at compilation time -and gain performance. Moreover, in this way you can make cross compilation -easier. Therefore the compiler semantics has practical advantages and -I am willing cope with it, even if conceptually I still prefer the -straightforwardness of interpreter semantics. +from runtime you can perform some computation only once (at compilation time) +and gain performance. This is not strange at all: compilers *are* +performance hacks. It is just more efficient to convert a a program into +machine code with a compiler than to interpret one expression at the time. +Since in practice there are lots of situations where performance is +important and one does need a compiler, it makes a lot of sense to +have a compiler semantics. The compiler +semantics is also designed to make separate compilation and cross compilation +possible. Therefore the compiler semantics +has many practical advantages and +I am willing cope with it, even if it is not as +straightforward as interpreter semantics. + Moreover, there are (non-portable) tricks to define helper functions at expand time without need to move them into a separate module, therefore -compiler semantics is not so unbearable. +it is not so difficult to work around the restrictions of the compiler +semantics. The thing I really dislike is full phase separation. But a full discussion of the issues releated to phase separation will require a whole episode. See you next week! -Therefore, if you have a compiled version of Scheme, -it makes sense to separate compilation time from runtime, and to -expand macros *before* compiling the helper functions (in absence of -phase separation, macros are still expanded before running any runtime -code, but *after* recognizing the helper functions). -Notice that Scheme has a concept of *macro expansion time* which is -valid even for interpreted implementation when there is no compilation -time. The `expansion process`_ of Scheme source code is specified in -the R6RS. - -There is still the question if strong phase separation is a good thing, -or if weak phase separation (as in Ikarus) is enough. For the programmer -weak phase separation is easier, since he does not need to specify -the phase in which he wants to import names. Strong phase separation -has been introduced so that at compile time a language which is -completely different from the language you use at runtime. In particular -you could decide to use in macros a subset of the full R6RS language. - -Suppose for instance you are a teacher, and you want to force your -students to write their macros using only a functional subset of Scheme. -You could then import at compile time all R6RS procedures except the -nonfunctional ones (like ``set!``) while keeping import at runtime -the whole R6RS. You could even perform the opposite, and remove ``set!`` -from the runtime, but allowing it at compile time. - -Therefore strong phase separation is strictly more powerful than week -phase separation, since it gives you more control. In Ikarus, when -you import a name in your module, the name is imported in all phases, -and there is nothing you can do about it. For instance this program -in Ikarus (but also IronScheme, Ypsilon, MoshScheme) - -.. code-block:: scheme - (import (rnrs) (for (only (aps list-utils) distinct?) expand)) - (display distinct?) - -runs, contrarily to what one would expect, because it is impossible -to import the name ``distinct?`` at expand time and not at runtime. -In PLT Scheme and Larceny instead the program will not run, as you -would expect. - -You may think the R6RS document to be schizophrenic, since it -accepts both implementations with phase separation and without -phase separation. The previous program is *conforming* R6RS code, but -behaves *differently* in R6RS-compliant implementations! - -but using the semantics without phase separation results in -non-portable code. Here a bold decision was required to ensure -portability: to declare the PLT semantics as the only acceptable one, -or to declare the Dibvig-Gouloum semantics as the only acceptable one. - -De facto, the R6RS document is the result -of a compromise between the partisans of phase separation -and absence of phase separation. - - -On the other hand strong phase separation makes everything more complicated: -it is somewhat akin to the introduction of multiple namespace, because -the same name can be imported in a given phase and not in another, -and that can lead to confusion. To contain the confusion, the R6RS -documents states that *the same name cannot be used in different phases -with different meaning in the same module*. -For instance, if the identifier ``x`` is bound to the -value ``v`` at the compilation time and ``x`` is defined even -at runtime, ``x`` must be bound to ``v`` even at runtime. However, it -is possible to have ``x`` bound at runtime and not at compile time, or -viceversa. This is a compromise, since PLT Scheme in non R6RS-compliant mode -can use different bindings for the same name at different phases. - -There are people in the Scheme community thinking that strong phase -separation is a mistake, and that weak phase separation is the right thing -to do. On the other side people (especially from the PLT community where -all this originated) sing the virtues of strong phase separation and say -all good things about it. I personally I have not seen a compelling -use case for strong phase separation yet. -On the other hand, I am well known for preferring simplicity over -(unneeded) power. - .. _expansion process: http://www.r6rs.org/final/html/r6rs/r6rs-Z-H-13.html#node_chap_10 |# diff --git a/artima/scheme/scheme21.ss b/artima/scheme/scheme21.ss index 513ce49..94251fb 100644 --- a/artima/scheme/scheme21.ss +++ b/artima/scheme/scheme21.ss @@ -1,8 +1,93 @@ -#|More on phase separation +#|Phase separation =================================================================== -In this episode I will discuss in detail the trickiness -associated with the concept of phase separation. +We saw in the last episode that Scheme programs executed +in compiler semantics exhibit phase separation, i.e. some code +is executed at compile (expand) time and some code is executed +at runtime. Things, however, are more complicated than that. + +There are two different concepts of phase +separation, even for R6RS-conforming implementations. +Ikarus, Ypsilon, IronScheme e MoshScheme have +a weak (partial) form of phase separation: there is a distinction +between expand time and runtime, but it is not possible to import +names only at runtime or only at expand time, i.e. the phases are +not fully separated. PLT Scheme and Larceny instead have a strong +form of phase separation in which phases are completely separated: +in such implementations, when you import (more correctly +*instantiate*) a module, you may specify in which phases to import +it, and each phases sees a *different instance* of the module. +The consequence is that the language used at at compile time (the +language seen by a piece of code is the sum of the imported names) +can be completely different from the language used at runtime. In particular +you could decide to use in macros a subset of the full R6RS language. + +Suppose for instance you are a teacher, and you want to force your +students to write their macros using only a functional subset of Scheme. +You could then import at compile time all R6RS procedures except the +nonfunctional ones (like ``set!``) while importing at runtime +the whole R6RS. You could even perform the opposite, and remove ``set!`` +from the runtime, but allowing it at compile time. + +Therefore strong phase separation is strictly *more powerful* than week +phase separation, since it gives you more control. In implementations +with weak/partial phase separation when +you import a name in your module, the name is imported in all phases, +and there is nothing you can do about it. For instance this program +in Ikarus (but also IronScheme, Ypsilon, MoshScheme) + +.. code-block:: scheme + + (import (rnrs) (for (only (aps list-utils) distinct?) expand)) + (display distinct?) + +runs, contrarily to what one would expect, because it is impossible +to import the name ``distinct?`` at expand time and not at runtime. +In PLT Scheme and Larceny instead the program will not run, as you +would expect. + +You may think the R6RS document to be schizophrenic, since it +accepts both implementations with phase separation and without +phase separation. The previous program is *conforming* R6RS code, but +behaves *differently* in R6RS-compliant implementations! + +but using the semantics without phase separation results in +non-portable code. Here a bold decision was required to ensure +portability: to declare the PLT semantics as the only acceptable one, +or to declare the Dibvig-Gouloum semantics as the only acceptable one. + +De facto, the R6RS document is the result +of a compromise between the partisans of phase separation +and absence of phase separation. + + +There is still the question if strong phase separation is a good thing, +or if weak phase separation (as in Ikarus) is enough. For the programmer +weak phase separation is easier, since he does not need to specify +the phase in which he wants to import names. +On the other hand strong phase separation makes everything more complicated: +it is somewhat akin to the introduction of multiple namespace, because +the same name can be imported in a given phase and not in another, +and that can lead to confusion. To contain the confusion, the R6RS +documents states that *the same name cannot be used in different phases +with different meaning in the same module*. +For instance, if the identifier ``x`` is bound to the +value ``v`` at the compilation time and ``x`` is defined even +at runtime, ``x`` must be bound to ``v`` even at runtime. However, it +is possible to have ``x`` bound at runtime and not at compile time, or +viceversa. This is a compromise, since PLT Scheme in non R6RS-compliant mode +can use different bindings for the same name at different phases. + +There are people in the Scheme community thinking that strong phase +separation is a mistake, and that weak phase separation is the right thing +to do. On the other side people (especially from the PLT community where +all this originated) sing the virtues of strong phase separation and say +all good things about it. I personally I have not seen a compelling +use case for strong phase separation yet. +On the other hand, I am well known for preferring simplicity over +(unneeded) power. + +.. _5: http://www.artima.com/weblogs/viewpost.jsp?thread=239699 More examples of macros depending on helper functions ----------------------------------------------------------------- @@ -69,24 +154,25 @@ The problem with auxiliary macros ------------------------------------------------------------------ We said a few times that auxiliary functions are not available to macros -defined in the same module, but actually in general +defined in the same module, but in general there is the *same* problem for any identifier which is used in the right hand side of a macro definition, *including auxiliary macros*. -For instance, we may regard the ``indexer-syntax`` macro -as an auxiliary macro, to be used in the right hand side of a -``def-syntax`` form. In systems with strong phase separation, like PLT Scheme and Larceny, auxiliary macros are not special, and they behave as auxiliary functions: you must put them into a separare module and you must import them with ``(for (only (module) helper-macro) expand)`` before using them. -This is why the following script + +In particular, the ``indexer-syntax`` macro defined before is +an auxiliary macro, to be used in the right hand side of a +``def-syntax`` form. The following script $$indexer-syntax: -fails in PLT scheme: +fails in PLT scheme +:: $ plt-r6rs indexer-syntax.ss indexer-syntax.ss:9:0: def-syntax: bad syntax in: (def-syntax (indexer-syntax a b c)) @@ -95,10 +181,10 @@ fails in PLT scheme: /usr/lib/plt/collects/rnrs/base-6.ss:492:6 -The problem is that in PLT and Larceny, the second ``def-syntax`` does +because the second ``def-syntax`` does not see the binding for the ``indexer-syntax`` macro. -This is a precise design choice: systems with strong phase +This is a precise design choice: systems with full phase separation are making the life harder for programmers, by forcing them to put auxiliary functions/macros/objects in auxiliary modules, to keep absolute control on how the @@ -119,53 +205,14 @@ is that a long as the compiler reads macro definitions, it expands the compile-time namespace of recognized names which are available to successive syntax definitions. -In Ikarus the script ``identifier-syntax.ss`` is +In such systems the script ``identifier-syntax.ss`` is perfectly valid: the first syntax definition would add a binding for ``identifier-syntax`` to the macro namespace, so that it would be seen by the second syntax definition. - -Systems with strong phase separation instead are effectively using -different namespaces for each phase. - - -Implementing a first class module system ------------------------------------------ - -This is the same as implementing a Pythonic interface over hash tables -or association lists. - -Working around phase separation --------------------------------------------------------------- - -I have always hated being forced to put my helper functions in an -auxiliary module, because I often use auxiliary functions which -are intended to be used only once inside a given macro, thus -it makes sense to put those auxiliary functions *in the same -module* as the macro the are used in. -In principle you could solve the problem by definining all the -functions *inside* the macro, but I hate this, both for dogmatic -reasons (it is a Pythonista dogma that *flat is better than nested*) -and for pragmatic reasons, i.e. I want to be able to debug my -helper functions and this is impossible if they are hidden inside -the macro. They must be available at the top-level. Moreover, you -never know, and a functionality which was intended for use in a specific -macro my turn useful for another macro after all, and it is much -more convenient if the functionality is already encapsulated in -a nice exportable top level function. - -I am not the only to believe that it should be possible to define -helper functions in the same module as the macro and actually -many Scheme implementations provide a way to do so via a -``define-for-syntax`` form which allows to define function -at *expand time*, so that they are available for usage in macros. - -If your Scheme does not provide ``define-for-syntax``, which is not -part of the R6RS specification, you can still work around phase -separation with some clever hack. For instance, you could -use the following macro: - -$$lang:literal-replace +In any case, if you want to write portable code, you must follow +the PLT/Larceny route, to put your auxiliary macros in a separated +module and to import them at expand time. |# (import (rnrs) (sweet-macros) (for (aps list-utils) expand run) diff --git a/artima/scheme/scheme23.ss b/artima/scheme/scheme23.ss new file mode 100644 index 0000000..989966a --- /dev/null +++ b/artima/scheme/scheme23.ss @@ -0,0 +1,229 @@ +#|Side effects in modules +=============================================================== + +In functional programs there are no side effects; in real life programs +however there are side effects, and we must be able to cope with them. +The way the module system copes with side effects is quite tricky and +implementation-dependent. The goal of this episode is to shed some +light on the subject. + +Side effects at the REPL +------------------------------------------------------ + +Let me start with a simple example. Consider a module exporting a variable +(``x``) and a function with side effects affecting that variable (``incr-x``)): + +$$experimental/mod1: + +(for convenience I am storing all the code of this episode code into a +package called ``experimental``). +This kind of side effect is ruled out by the R6RS specification, since +exported variables must be immutable. This is the reason why both Ikarus +and Ypsilon reject the code with errors like +``attempt to export mutated variable`` or +``attempt to modify immutable variable``. +On the other hand PLT Scheme compiles the code without raising any +warning, therefore even this simple case is tricky and exposes +a non-portability of the module system :-( + +Consider now a module exporting a function with side effects affecting a +non-exported variable: + +$$experimental/mod2: + +This is a valid library which compiles correctly. The accessor ``get-x`` +gives access to the internal variable ``x``. We may import it +at the REPL and experiment with it: + +.. code-block:: scheme + + > (import (experimental mod2)) + > (get-x) + 0 + > (incr-x) + 1 + > (incr-x) + 2 + > (get-x) + 2 + +Everything works as you would expect. + +As always, things are trickier in scripts, when compiler semantics and +phase separation enters in the game. + +Side effects and phase separation +------------------------------------------------------ + +Consider the following script: + +$$experimental/use-mod2: + +Here we import the module ``mod2`` twice, both at run-time and at expand time. +In Scheme implementations with full phase separation there are two fully +separated instances of the module, and running the script returns +what you would expect:: + + $ plt-r6rs use-mod2.ss + At expand-time x=1 + At run-time x=1 + +The fact that ``x`` was incremented at compile-time has no effect +at all at run-time, since the run-time variable ``x`` belongs to a completely +different instance of the module. In system with weak phase separation +instead, there is only a *single instance of the module for all phases*, +so that incrementing ``x`` at expand-time has effect at runtime:: + + $ ikarus --r6rs-script use-mod2.ss + At expand-time x=1 + At run-time x=2 + +You would get the same with Ypsilon. + +This only works because the script is executed immediately +after compilation *in the same process*. However, having compile-time +effects affecting run-time values is *utterly wrong*, since it breaks +separate compilation. If we turn the script into a library and we +compile it separately, it is clear than the run-time value of ``x`` +cannot be affected by the compile-time value of ``x`` +(maybe the code was compiled 10 years ago!) and it +must use a separate instance of the imported module. + +Side effects and separate compilation +------------------------------------------------------------- + +Let me explain in detail how separate compilation works in Ikarus, +Ypsilon and PLT Scheme. Suppose we turn the previous script into a library + +$$experimental/use-mod3: + +and let us invoke this library though a script ``use-mod3.ss``: + +$$experimental/use-mod3: + +If we use PLT Scheme, nothings changes:: + + $ plt-r6rs use-mod3.ss + At expand-time x=1 + At run-time x=1 + +This is expected: turning a script into a library did not make +anything magic happens. On the other hand, things are very +different if we run the same code under Ypsilon. +The first time the script is run it prints three lines:: + + $ ypsilon --r6rs use-mod3.ss + At expand-time x=1 + At expand-time x=2 + At run-time x=3 + +However, if we run the script again it prints just one line:: + + $ ypsilon --r6rs use-mod3.ss + At run-time x=1 + +The reason is that the first time Ypsilon compiles the libraries, using +the same module instance, so that there is a single ``x`` variable which +is incremented twice at expand time - the first time when ``mod2`` +is imported and the second time when ``mod3`` is imported - and +once at run-time. The second time there is nothing +to recompile, so only the runtime ``x`` variable is incremented, and +there is no reference to the compile time instance. + +The situation for Ikarus is subtler. Apparently we get the same +as before, when ``mod3`` was just a script:: + + $ ikarus --r6rs-script use-mod3.ss + At expand-time x=1 + At run-time x=2 + +However, this only happens because Ikarus is compiling all the libraries +at the same time. If we use separate compilation we get:: + + $ ikarus --compile-dependencies use-mod3.ss + At expand-time x=1 + Serializing "/home/micheles/gcode/scheme/experimental/mod3.sls.ikarus-fasl" ... + Serializing "/home/micheles/gcode/scheme/experimental/mod2.sls.ikarus-fasl" ... + +As you see, ``mod2`` the message ``At expand-time x=1`` is printed when +``mod2`` is compiled. If we run the script ``use-mod3.ss`` now, we +get just the runtime message:: + + $ ikarus --r6rs-script use-mod3.ss + At run-time x=1 + +Both in Ikarus and in Ypsilon, the same invocation of the script +returns different results, depending if the libraries have been +precompiled or not. This is ugly and error prone. The full phase +separation mechanism of PLT Scheme has been designed to avoid this +problem: in PLT (and Larceny) one consistently gets always the same +result. That is good. + +However, I think that both the PLT/Larceny people and the Ikarus/Ypsilon +people are wrong. The PLT/Larceny people are wrong since full phase +separation causes more problems than it solves: it is not the +right solution to this problem. On the other hand, the Ikarus/Ypsilon +people are wrong because they lack separate instantiation +of modules. However, it would be trivial to get separate instantiation +of modules for Ikarus and Ypsilon: it is enough to spawn +two separate processes, run one after the other: the +first to compile the script and its libraries, and the second to +execute it. That would make sure that incrementing +``x`` in the expansion phase could not influence the value of ``x`` +at runtime: in this way they could get repeatable results. + +In other words, in my own opinionated view the implementations +with partial phase separation are much closer to the right path than +implementation with full phase separation. + + -------------------------------------------------------------------------- +| | single instantiation | multiple instantiation | + -------------------------------------------------------------------------- +| partial phase separation | not so bad | good | +| full phase separation | bad (Larceny) | bad (PLT) | + -------------------------------------------------------------------------- + + +|# + +(import (rnrs) (sweet-macros) (for (aps lang) expand) (aps compat)) + +;;DEF-BOOK +(def-syntax (def-book name title author) + (: with-syntax + name-title (identifier-append #'name "-title") + name-author (identifier-append #'name "-author") + #'(begin + (define name (vector title author)) + (define name-title (vector-ref name 0)) + (define name-author (vector-ref name 1))))) + +;;END +(pretty-print (syntax-expand (def-book bible "The Bible" "God"))) + + + ;;TEST-DEF-BOOK + (test "def-book" + (let () + (def-book bible "The Bible" "God") + (list bible-title bible-author)) + (list "The Bible" "God")) + ;;END + + +;;ALIST2 +(def-syntax (alist2 arg ...) + (: with-syntax ((name value) ...) (normalize #'(arg ...)) + (if (for-all identifier? #'(name ...)) + #'(let* ((name value) ...) + (list (list 'name name) ...)) + (syntax-violation 'alist "Found non identifier" #'(name ...) + (remp identifier? #'(name ...)))))) +;;END + +(run + (let ((a 1)) + (test "mixed" + (alist2 a (b (* 2 a))) + '((a 1) (b 2)))) + ) diff --git a/artima/scheme/scheme27.ss b/artima/scheme/scheme27.ss deleted file mode 100644 index 81ba924..0000000 --- a/artima/scheme/scheme27.ss +++ /dev/null @@ -1,180 +0,0 @@ -#|Phase separation -=============================================================== - -Phase separation -------------------------------------------------- - -Phase separation is one of the trickiest concepts in Scheme macrology, -and perhaps the one that gave me the most headaches when I was learning -macros. It still beats me sometimes. Actually, the concept it is not -that difficult, it is its practical implementation which is extremely -tricky because it is *unspecified by the Scheme standard* and -totally *implementation-dependendent*: worse than -that, the *same* implementation can implement phase separation *differently* -in compiled code and in interpreted code, and/or differently in the REPL -and in scripts! - - -If you have a macro depending on helper functions, like -the previous one, you must put the helper functions in a separated -module if you want to ensure portability. Moreover, you must -import the helper functions with the syntax ``(for (module-name) expand)`` -meaning that the helper functions are intended to be used at expand -time, in macros. Ikarus is quite forgiving and can just use a regular -import, but PLT Scheme and Larceny will raise an error if you do not -use the ``for expand``. A full description of the module system, with -all the gory details, will require six more episodes, and will constitute -part V of these *Adventures*. - - -Consider for instance this example in Ypsilon, which tries to implement -a macro registry: - -$$registry.ypsilon: - -You can run the example and you will get - -``registry: ((#<syntax m>)`` - -as result (notice however that if you comment out the macro use, i.e. -the ``(m)`` line, the registry will *not* be populated). -So everything seems to work as one would expect. -However, if you try to run the same -example in Ikarus or in PLT Scheme or in most other R6RS Scheme -implementations you will get an error. Let me -show the PLT error message message, which is rather -clear if you understand what phase separation is, wheread -the Ikarus error message is somewhat misleading for reasons misterious to me:: - - $ plt-r6rs registry.ypsilon.ss - registry.ypsilon.ss:10:5: compile: unbound variable in module - (in the transformer environment, which does not include the - run-time definition) in: register - -Ypsilon, as most interpreted Scheme implementations, has no phase separation: -there is no big difference between macros and functions, which are -simply recognized in the order given by their position in the source code. -In our example the ``register`` function comes before the ``m`` macro, -so it can be used in the right hand side of the macro definition. - -An example will clarify the point. Suppose we define a registry module -as follows - -$$experimental/registry: - -(for convenience I am storing all this -code in a package called ``experimental``) and suppose we use it as follows: - -$$use-registry.ikarus: - -In Ikarus everything works fine (in Ypsilon too of course) -and running the script will return you something like - -:: - - registering #<syntax m [char 83 of use-registry.ikarus.ss]> - (#<syntax m [char 83 of use-registry.ikarus.ss]>) - -(notice the annotation about the position of the identifier ``m`` in -the source code of the script, a signature of the fact that ``m`` -is a bona fide syntax object). - -In PLT Scheme instead running the script raise an error:: - - $ plt-r6rs use-registry.ikarus.ss - use-registry.ikarus.ss:5:5: compile: unbound variable in module - (transformer environment) in: register - -.. image:: salvador-dali-clock.jpg - -The problem is that PLT Scheme has *strong phase separation*: by default -names defined in external modules are imported *only* at runtime. -In some sense this is absurd since -names defined in an external pre-compiled modules -are of course known at compile time -(this is why Ikarus has no trouble to import them at compile time); -nevertheless PLT Scheme and Larceny Scheme forces you to specify -at which phase the functions must be imported. If you want to import -them at expansion time (the time when macros are processed; often -incorrectly used as synonymous for compilation time) you must say so: - -$$use-registry.mzscheme: - -Notice also that I did specify importation at run time for the -``registry`` function, since it is called at runtime, i.e. not inside -macros. If you run this script you will get:: - - $ plt-r6rs use-registry.mzscheme.ss - registering #<syntax:/home/micheles/gcode/artima/scheme/use-registry.mzscheme.ss:8:16> - () - -The PLT Scheme representation of syntax objects shows the line number and -the column number, therefore you should interpret the previous out as -*I am registering the syntax object defined in the source file -use-registry.mzscheme.ss, at line 8 and column 16*, which corresponds -to the identifier ``m``. - -This is close, but not quite cigar. The script now runs, but it -returns a rather unexpected empty list. The reason why the registry is -empty has nothing to do with phase separation, but rather -another "feature" of PLT Scheme (and only of PLT Scheme) which goes -under the name of multiple instantiation of modules. In practice, -importing a module in PLT Scheme imports *an independent -copy (instance) of the original module*. - -The imported instance of the module includes a copy of all bindings -defined in the original module, -*including the internal bindings which are not exported*. -This remark explains why the registry is empty: the ``register`` -functions changes the ``_registry`` list in the *current* instance, -but the value of ``_registry`` in the *original* instance (i.e. the -value returned by the ``(registry)`` function) is left unchanged and -is the same as at the beginning, i.e. the empty list. - -However, I do not want to -complicate the explanation of phase separation now, which is already -complicated as it is, so let me defer a full explanation of this point -to a future episode of my *Adventures*. -|# - -(import (rnrs) (sweet-macros) (for (aps lang) expand) (aps compat)) - -;;DEF-BOOK -(def-syntax (def-book name title author) - (: with-syntax - name-title (identifier-append #'name "-title") - name-author (identifier-append #'name "-author") - #'(begin - (define name (vector title author)) - (define name-title (vector-ref name 0)) - (define name-author (vector-ref name 1))))) - -;;END -(pretty-print (syntax-expand (def-book bible "The Bible" "God"))) - - - ;;TEST-DEF-BOOK - (test "def-book" - (let () - (def-book bible "The Bible" "God") - (list bible-title bible-author)) - (list "The Bible" "God")) - ;;END - - -;;ALIST2 -(def-syntax (alist2 arg ...) - (: with-syntax ((name value) ...) (normalize #'(arg ...)) - (if (for-all identifier? #'(name ...)) - #'(let* ((name value) ...) - (list (list 'name name) ...)) - (syntax-violation 'alist "Found non identifier" #'(name ...) - (remp identifier? #'(name ...)))))) -;;END - -(run - (let ((a 1)) - (test "mixed" - (alist2 a (b (* 2 a))) - '((a 1) (b 2)))) - ) diff --git a/artima/scheme/table-of-contents.txt b/artima/scheme/table-of-contents.txt index e5ad2fe..54b7b2a 100644 --- a/artima/scheme/table-of-contents.txt +++ b/artima/scheme/table-of-contents.txt @@ -41,7 +41,7 @@ Part III - Functional Aspects Ep18_ Streams -Part IV - Advanced Macrology +Part IV - The Module System .. _Ep1: http://www.artima.com/weblogs/viewpost.jsp?thread=238789 .. _Ep2: http://www.artima.com/weblogs/viewpost.jsp?thread=238941 diff --git a/artima/scheme/use-ct-mapping.ikarus.ss b/artima/scheme/use-ct-mapping.ikarus.ss deleted file mode 100644 index a328e8e..0000000 --- a/artima/scheme/use-ct-mapping.ikarus.ss +++ /dev/null @@ -1,8 +0,0 @@ -(import (rnrs) (sweet-macros) (experimental ct-mapping)) - -(def-syntax color (ct-mapping (red #\R) (green #\G) (yellow #\Y))) - -(display "Available colors: ") -(display (color <names>)) -(display (list (color red) (color green) (color yellow))) -(newline) diff --git a/artima/scheme/use-ct-mapping.mzscheme.ss b/artima/scheme/use-ct-mapping.ss index 2d16acb..2d16acb 100644 --- a/artima/scheme/use-ct-mapping.mzscheme.ss +++ b/artima/scheme/use-ct-mapping.ss diff --git a/artima/scheme/use-registry.ikarus.ss b/artima/scheme/use-registry.ikarus.ss deleted file mode 100644 index 59edde1..0000000 --- a/artima/scheme/use-registry.ikarus.ss +++ /dev/null @@ -1,9 +0,0 @@ -(import (rnrs) (sweet-macros) (experimental registry)) - -(def-syntax m - (begin - (register #'m) - (syntax-match () (sub (m) #'42)))) - -(m) -(display (registry)) diff --git a/artima/scheme/use-registry.mzscheme.ss b/artima/scheme/use-registry.mzscheme.ss deleted file mode 100644 index eaa803d..0000000 --- a/artima/scheme/use-registry.mzscheme.ss +++ /dev/null @@ -1,12 +0,0 @@ -#!r6rs -(import (rnrs) (sweet-macros) - (for (only (experimental registry) register) expand) - (for (only (experimental registry) registry) run)) - -(def-syntax m - (begin - (register #'m) - (syntax-match () (sub (m) #'42)))) - -(m) -(display (registry)) diff --git a/artima/scheme/use-registry.ss b/artima/scheme/use-registry.ss new file mode 100644 index 0000000..c480601 --- /dev/null +++ b/artima/scheme/use-registry.ss @@ -0,0 +1,13 @@ +#!r6rs +(import (rnrs) (sweet-macros) (only (aps compat) printf) + (for (only (experimental registry) register) expand) + (for (only (experimental registry) registry) run) + (experimental def-m1)) + +(def-syntax m2 + (begin + (register #'m2) + (syntax-match () (sub (m2) #'2)))) + +(m2) +(printf "Registered ~a macro(s)\n" (length (registry))) |