On 2012-03-06, at 10:44 AM, Álvaro Castro-Castilla wrote:
Thank you Marc! Well, that certainly gives me a lot of possibilities, so I can keep using syntax-rules.
But I didn't state my question well. The thing that I don't understand is why when loading the psyntax extension, these files here work, but without psyntax, the push! macro is undefined. What's modifying psyntax that makes something that doesn't work in psyntax-less gambit actually work. I was thinking that it might do with environments, but macros are all in the same environment, right?
alvatar@asia~ $ cat testA.scm (define-macro (push! list obj) `(set! ,list (cons ,obj ,list)))
(load "testB")
alvatar@asia~ $ cat testB.scm (define l '(b c)) (push! l 'a) (pp l)
alvatar@asia~ $ gsc -e '(load "testA")' *** ERROR IN "testB.scm"@2.2 -- Unbound variable: push!
alvatar@asia~ $ gsc -:s -e '(load "testA")' (a b c)
Did I express the question properly?
Thanks again for your help and invaluable explanations, Marc.
As I have said previously the psyntax macro-expander creates a new dialect of Scheme on top of Gambit. The macros that are defined when using syntax-rules and syntax-case are completely handled by psyntax, and the Gambit macro-expansion system is *not* used. Even the define-macro is rewritten into a use of define-syntax and syntax-case. The difference in behaviour that you are witnessing is due to the way the psyntax authors believe macros should work. In terms of implementation there is probably one global environment where toplevel macros are registered. So a macro defined in one file, will be visible in another file that is "load"ed. This is fine for an interpreter or a system like ChezScheme which uses a just-in-time compiler (Scheme code is compiled in the order that it is visited, either at the REPL or in files that are "load"ed).
This model is not well adapted for Gambit which compiles files ahead of time. So Gambit uses a model where macro definitions are local to each compilation unit (similarly to macros in the C language). If you define macro M in a Scheme file A.scm, it is only visible in the file A.scm . However, Gambit has an "include" special form which allows splicing Scheme code from a file into multiple other Scheme files. If (include "A.scm") is used in file B.scm, then macro M defined in A.scm will be visible in B.scm (simply because the include essentially duplicates the code into B.scm).
Note that there is a *huge* difference between a (include "A.scm") and (load "A.scm") in B.scm . The load *procedure* is called when the program is executed to load the code from A.scm, whereas the include *special form* is handled at macro expansion time, so the code from A.scm, including the macro definition of M, is visible to the Scheme compiler. Imagine the following Scheme program B.scm:
;; B.scm (load (string-append (read) ".scm")) (print (M 1 2 3))
Let's say you compile this program with
gsc B
and then run it with
gsi B
The program will wait until you enter a string, say "A", and then proceed to append ".scm" and load that file. So it will load the file A.scm . The compiler could not know that A.scm was the file that would be loaded by (load (string-append (read) ".scm")), so even though A.scm defines the macro M, it will not affect the call (M 1 2 3). The compiler will have assumed when compiling B.scm that M is a global variable and that (M 1 2 3) is a procedure call to the procedure contained in M.
You can't do (include (string-append (read) ".scm")), because include is a special form and its sole argument must be a literal string naming the file to include. The closest you can get is
;; B.scm (define-macro (foo) `(include ,(string-append (read) ".scm"))) (foo) (print (M 1 2 3))
It is when you *compile* this program with
gsc B
that the read procedure is called and you have to enter a string. If you enter "A", then the file "A.scm" will be included and the call (M 1 2 3) will expand accordingly to the macro definition of M in A.scm . No string will be read when you execute the program with
gsi B
In other words, load and include have completely different binding times in Gambit.
Does this clarify things?
Marc
Afficher les réponses par date
Marc, this clarifies everything and finally I have a clear picture of how this works. I really didn't expect that the syntax-rules and syntax-case system was implemented like that, even affecting define-macro. It seems that it can affect code efficiency as well. Probably is better to resort to define-macros and re-implement those from syntax-rules in terms of define-macros.
Thank you very much for the great explanation.
2012/3/6 Marc Feeley feeley@iro.umontreal.ca:
On 2012-03-06, at 10:44 AM, Álvaro Castro-Castilla wrote:
Thank you Marc! Well, that certainly gives me a lot of possibilities, so I can keep using syntax-rules.
But I didn't state my question well. The thing that I don't understand is why when loading the psyntax extension, these files here work, but without psyntax, the push! macro is undefined. What's modifying psyntax that makes something that doesn't work in psyntax-less gambit actually work. I was thinking that it might do with environments, but macros are all in the same environment, right?
alvatar@asia~ $ cat testA.scm (define-macro (push! list obj) `(set! ,list (cons ,obj ,list)))
(load "testB")
alvatar@asia~ $ cat testB.scm (define l '(b c)) (push! l 'a) (pp l)
alvatar@asia~ $ gsc -e '(load "testA")' *** ERROR IN "testB.scm"@2.2 -- Unbound variable: push!
alvatar@asia~ $ gsc -:s -e '(load "testA")' (a b c)
Did I express the question properly?
Thanks again for your help and invaluable explanations, Marc.
As I have said previously the psyntax macro-expander creates a new dialect of Scheme on top of Gambit. The macros that are defined when using syntax-rules and syntax-case are completely handled by psyntax, and the Gambit macro-expansion system is *not* used. Even the define-macro is rewritten into a use of define-syntax and syntax-case. The difference in behaviour that you are witnessing is due to the way the psyntax authors believe macros should work. In terms of implementation there is probably one global environment where toplevel macros are registered. So a macro defined in one file, will be visible in another file that is "load"ed. This is fine for an interpreter or a system like ChezScheme which uses a just-in-time compiler (Scheme code is compiled in the order that it is visited, either at the REPL or in files that are "load"ed).
This model is not well adapted for Gambit which compiles files ahead of time. So Gambit uses a model where macro definitions are local to each compilation unit (similarly to macros in the C language). If you define macro M in a Scheme file A.scm, it is only visible in the file A.scm . However, Gambit has an "include" special form which allows splicing Scheme code from a file into multiple other Scheme files. If (include "A.scm") is used in file B.scm, then macro M defined in A.scm will be visible in B.scm (simply because the include essentially duplicates the code into B.scm).
Note that there is a *huge* difference between a (include "A.scm") and (load "A.scm") in B.scm . The load *procedure* is called when the program is executed to load the code from A.scm, whereas the include *special form* is handled at macro expansion time, so the code from A.scm, including the macro definition of M, is visible to the Scheme compiler. Imagine the following Scheme program B.scm:
;; B.scm (load (string-append (read) ".scm")) (print (M 1 2 3))
Let's say you compile this program with
gsc B
and then run it with
gsi B
The program will wait until you enter a string, say "A", and then proceed to append ".scm" and load that file. So it will load the file A.scm . The compiler could not know that A.scm was the file that would be loaded by (load (string-append (read) ".scm")), so even though A.scm defines the macro M, it will not affect the call (M 1 2 3). The compiler will have assumed when compiling B.scm that M is a global variable and that (M 1 2 3) is a procedure call to the procedure contained in M.
You can't do (include (string-append (read) ".scm")), because include is a special form and its sole argument must be a literal string naming the file to include. The closest you can get is
;; B.scm (define-macro (foo) `(include ,(string-append (read) ".scm"))) (foo) (print (M 1 2 3))
It is when you *compile* this program with
gsc B
that the read procedure is called and you have to enter a string. If you enter "A", then the file "A.scm" will be included and the call (M 1 2 3) will expand accordingly to the macro definition of M in A.scm . No string will be read when you execute the program with
gsi B
In other words, load and include have completely different binding times in Gambit.
Does this clarify things?
Marc