* Anything better for delayed lexical evaluation than (lambda () ...)? @ 2011-12-03 15:45 David Kastrup 2011-12-03 16:44 ` Andy Wingo ` (2 more replies) 0 siblings, 3 replies; 82+ messages in thread From: David Kastrup @ 2011-12-03 15:45 UTC (permalink / raw) To: guile-devel Hi, if I have something read that is evaluated later, the lack of procedure-environment in Guilev2 implies that I have to wrap the stuff in (lambda () ...) in order to capture the lexical environment for evaluation. Is it possible to have a shortcut (make-closure ...) or so for that purpose? The reason is that if ... is a call to a procedure-with-setter, (lambda () ...) actually does not cut it for capturing the semantics of ..., and I need (make-procedure-with-setter (lambda () ...) (lambda (x) (set! ... x))) But x is not hygienic, so this is again too simplistic. And a separate macro make-closure also could decide that the expression is pure anyway and not go to the pain of creating an actual closure. In any way, using (lambda () ...) might have more cases where it just is not equivalent to the lexical expression inside when macros come into play. -- David Kastrup ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-03 15:45 Anything better for delayed lexical evaluation than (lambda () ...)? David Kastrup @ 2011-12-03 16:44 ` Andy Wingo 2011-12-06 14:55 ` Thien-Thi Nguyen 2011-12-06 19:50 ` Marco Maggi 2 siblings, 0 replies; 82+ messages in thread From: Andy Wingo @ 2011-12-03 16:44 UTC (permalink / raw) To: David Kastrup; +Cc: guile-devel On Sat 03 Dec 2011 16:45, David Kastrup <dak@gnu.org> writes: > Hi, if I have something read that is evaluated later, the lack of > procedure-environment in Guilev2 implies that I have to wrap the stuff > in (lambda () ...) in order to capture the lexical environment for > evaluation. > > Is it possible to have a shortcut (make-closure ...) or so for that > purpose? The reason is that if ... is a call to a > procedure-with-setter, (lambda () ...) actually does not cut it for > capturing the semantics of ..., and I need > (make-procedure-with-setter (lambda () ...) > (lambda (x) (set! ... x))) > > But x is not hygienic, so this is again too simplistic. And a separate > macro make-closure also could decide that the expression is pure anyway > and not go to the pain of creating an actual closure. I'm having a hard time parsing this, as it is very high level. Could you give an example of what you are trying to do? Thanks, Andy -- http://wingolog.org/ ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-03 15:45 Anything better for delayed lexical evaluation than (lambda () ...)? David Kastrup 2011-12-03 16:44 ` Andy Wingo @ 2011-12-06 14:55 ` Thien-Thi Nguyen 2011-12-06 15:45 ` David Kastrup 2011-12-06 19:50 ` Marco Maggi 2 siblings, 1 reply; 82+ messages in thread From: Thien-Thi Nguyen @ 2011-12-06 14:55 UTC (permalink / raw) To: David Kastrup; +Cc: guile-devel () David Kastrup <dak@gnu.org> () Sat, 03 Dec 2011 16:45:06 +0100 The lack of procedure-environment in Guilev2 implies that I have to wrap the stuff in (lambda () ...) in order to capture the lexical environment for evaluation. Is it possible to have a shortcut (make-closure ...) or so for that purpose? Does ‘delay’ (and later ‘force’) work? ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-06 14:55 ` Thien-Thi Nguyen @ 2011-12-06 15:45 ` David Kastrup 0 siblings, 0 replies; 82+ messages in thread From: David Kastrup @ 2011-12-06 15:45 UTC (permalink / raw) To: guile-devel Thien-Thi Nguyen <ttn@gnuvola.org> writes: > () David Kastrup <dak@gnu.org> > () Sat, 03 Dec 2011 16:45:06 +0100 > > The lack of procedure-environment in Guilev2 implies that I > have to wrap the stuff in (lambda () ...) in order to capture > the lexical environment for evaluation. > > Is it possible to have a shortcut (make-closure ...) or so for that > purpose? > > Does ‘delay’ (and later ‘force’) work? No. (set! (force (delay (some-procedure-with-setter and its args)))) does not work. In addition, each expression will be run once or not at all (and its side effects on the containing environment are desired), so the memoizing nature of promises does not help. -- David Kastrup ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-03 15:45 Anything better for delayed lexical evaluation than (lambda () ...)? David Kastrup 2011-12-03 16:44 ` Andy Wingo 2011-12-06 14:55 ` Thien-Thi Nguyen @ 2011-12-06 19:50 ` Marco Maggi 2011-12-11 9:33 ` David Kastrup 2 siblings, 1 reply; 82+ messages in thread From: Marco Maggi @ 2011-12-06 19:50 UTC (permalink / raw) To: David Kastrup; +Cc: guile-devel David Kastrup wrote: > Hi, if I have something read that is evaluated later, the > lack of procedure-environment in Guilev2 implies that I > have to wrap the stuff in (lambda () ...) in order to > capture the lexical environment for evaluation. Sorry to step in without an answer. What are you trying to do? What I understand is that a Scheme program reads some expressions and tries to evaluate them in a specific context of the program. Are you looking for a way to do something like the following chunk I found on the Net? (define x 0) (define clo (let ((x 1)) (lambda () '()))) (local-eval 'x (procedure-environment clo)) => 1 -- Marco Maggi ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-06 19:50 ` Marco Maggi @ 2011-12-11 9:33 ` David Kastrup 2011-12-11 9:51 ` David Kastrup 2011-12-12 5:21 ` Mark H Weaver 0 siblings, 2 replies; 82+ messages in thread From: David Kastrup @ 2011-12-11 9:33 UTC (permalink / raw) To: guile-devel Marco Maggi <marco.maggi-ipsu@poste.it> writes: > David Kastrup wrote: >> Hi, if I have something read that is evaluated later, the >> lack of procedure-environment in Guilev2 implies that I >> have to wrap the stuff in (lambda () ...) in order to >> capture the lexical environment for evaluation. > > Sorry to step in without an answer. What are you trying to > do? What I understand is that a Scheme program reads some > expressions and tries to evaluate them in a specific context > of the program. Are you looking for a way to do something > like the following chunk I found on the Net? > > (define x 0) > (define clo > (let ((x 1)) > (lambda () '()))) > (local-eval 'x (procedure-environment clo)) > => 1 It is more like (define (myeval what) (let* ((x 1) (clo (procedure-environment (lambda () #f)))) (local-eval (read (open-input-string what)) clo))) (myeval "(+ x 3)") Basically a string evaluation of a string that will be captured with read-hash-extend in our application. In practice, _both_ the environment created by (let* ((x 1)) ...) as well as the string to be interpreted later are written by the user, but they are spliced together at quite different points of time since the environment from which the string for myeval gets delivered is available only when the definition is being executed, not yet at its definition time. Basically I need to evaluate dynamic code in a given lexical environment rather than at top and/or module level. For a language that is supposed to be a building block for extension languages, not really a concept that is all that unusual I would think. -- David Kastrup ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-11 9:33 ` David Kastrup @ 2011-12-11 9:51 ` David Kastrup 2011-12-12 5:21 ` Mark H Weaver 1 sibling, 0 replies; 82+ messages in thread From: David Kastrup @ 2011-12-11 9:51 UTC (permalink / raw) To: guile-devel David Kastrup <dak@gnu.org> writes: > Marco Maggi <marco.maggi-ipsu@poste.it> writes: > >> David Kastrup wrote: >>> Hi, if I have something read that is evaluated later, the >>> lack of procedure-environment in Guilev2 implies that I >>> have to wrap the stuff in (lambda () ...) in order to >>> capture the lexical environment for evaluation. >> >> Sorry to step in without an answer. What are you trying to >> do? What I understand is that a Scheme program reads some >> expressions and tries to evaluate them in a specific context >> of the program. Are you looking for a way to do something >> like the following chunk I found on the Net? >> >> (define x 0) >> (define clo >> (let ((x 1)) >> (lambda () '()))) >> (local-eval 'x (procedure-environment clo)) >> => 1 > > It is more like > (define (myeval what) > (let* ((x 1) > (clo (procedure-environment (lambda () #f)))) > (local-eval (read (open-input-string what)) clo))) > > (myeval "(+ x 3)") > > Basically a string evaluation of a string that will be captured with > read-hash-extend in our application. > > In practice, _both_ the environment created by (let* ((x 1)) ...) as > well as the string to be interpreted later are written by the user, but > they are spliced together at quite different points of time since the > environment from which the string for myeval gets delivered is available > only when the definition is being executed, not yet at its definition > time. > > Basically I need to evaluate dynamic code in a given lexical environment > rather than at top and/or module level. > > For a language that is supposed to be a building block for extension > languages, not really a concept that is all that unusual I would think. To come back to the original request: > Is it possible to have a shortcut (make-closure ...) or so for that > purpose? The reason is that if ... is a call to a > procedure-with-setter, (lambda () ...) actually does not cut it for > capturing the semantics of ..., and I need > (make-procedure-with-setter (lambda () ...) > (lambda (x) (set! ... x))) I now implement this more or less as (define clo #t) (define (myeval what) (let* ((x 1)) (set! clo (list (cons 'x (lambda () x)))) (primitive-eval (read (open-input-string what))))) (myeval "(+ ((assq-ref clo 'x)) 3)") But of course if I want to translate something like (set! x 7) (also when x is something like (myprop k 'g) or so) with that technique, it falls down again. So in short, doing that sort of stuff by prewrapping all conceivable evaluation candidates into (lambda () ...) and doing source code location association at runtime to figure out which lambda to call is quite icky and more restricted than actually capturing an environment. See <URL:http://git.savannah.gnu.org/gitweb/?p=lilypond.git;a=blob;f=scm/parser-ly-from-scheme.scm;h=0e697d22bda657f3e970efa0281b01a0cd56360c;hb=HEAD> for the actual current source code that pushes small lambda capsules into the variable "closures" instead of just capturing a single local procedure-environment for _all_ parts of the string that is going to be parsed and interpreted at run time including small Scheme scraps. This is not hypothetical, but bonafide code running in a production environment. -- David Kastrup ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-11 9:33 ` David Kastrup 2011-12-11 9:51 ` David Kastrup @ 2011-12-12 5:21 ` Mark H Weaver 2011-12-12 6:47 ` David Kastrup 1 sibling, 1 reply; 82+ messages in thread From: Mark H Weaver @ 2011-12-12 5:21 UTC (permalink / raw) To: David Kastrup; +Cc: guile-devel David Kastrup <dak@gnu.org> writes: > Basically I need to evaluate dynamic code in a given lexical environment > rather than at top and/or module level. > > For a language that is supposed to be a building block for extension > languages, not really a concept that is all that unusual I would think. Guile 2 is an excellent base for building extension languages, but not in the way that you'd like to do it. Unfortunately, I see no way to support `procedure-environment' on arbitrary procedures without abandoning our optimizing compiler and going back to a simple evaluator. I suspect it would be possible to implement a special form that captures its lexical environment in such a way that arbitrary code could later be evaluated within that lexical environment. The presence of this special form would impose onerous constraints on the optimizer within the top-level form containing it. In fact, I can't think of an optimization that would still be possible, because the compiler would have to assume the worst: that some other thread could, at any time, mutate any lexical variable or call any lexical procedure visible from the special form. It gets even worse when you consider first-class continuations. I believe that this is the wrong approach, though it may be worth considering for the sake of allowing Lilypond to continue using its existing implementation strategy. In general, the _right_ way to build a custom extension language using Guile 2 is to write a compiler that converts your language into one of the other languages that Guile 2 supports. If there's something about Lilypond's language that you believe would make compilation impractical, let's talk about it. Maybe the Guile experts on this list can find a clever solution, or else maybe we can enhance Guile to support Lilypond's language in a straightforward manner. I would be glad to help with this. In the long run, it might be less work for us Guile hackers to implement a nice compiler for Lilypond than to implement and forever maintain the "capture-lexical-environment" special form, and it would almost certainly have better results. Regards, Mark ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-12 5:21 ` Mark H Weaver @ 2011-12-12 6:47 ` David Kastrup 2011-12-12 18:29 ` Mark H Weaver 0 siblings, 1 reply; 82+ messages in thread From: David Kastrup @ 2011-12-12 6:47 UTC (permalink / raw) To: Mark H Weaver; +Cc: guile-devel Mark H Weaver <mhw@netris.org> writes: > David Kastrup <dak@gnu.org> writes: >> Basically I need to evaluate dynamic code in a given lexical environment >> rather than at top and/or module level. >> >> For a language that is supposed to be a building block for extension >> languages, not really a concept that is all that unusual I would think. > > Guile 2 is an excellent base for building extension languages, but not > in the way that you'd like to do it. Unfortunately, I see no way to > support `procedure-environment' on arbitrary procedures without > abandoning our optimizing compiler and going back to a simple > evaluator. Sure. In an optimizing compiler, I would expect "procedure-environment" to only contain actually used parts of the environment, and that would make (procedure-environment (lambda () '())) fabulously useless. And of course I am not interested in the environment of that procedure, but rather in that where procedure-environment is called. > I suspect it would be possible to implement a special form that > captures its lexical environment in such a way that arbitrary code > could later be evaluated within that lexical environment. The > presence of this special form would impose onerous constraints on the > optimizer within the top-level form containing it. In fact, I can't > think of an optimization that would still be possible, because the > compiler would have to assume the worst: that some other thread could, > at any time, mutate any lexical variable or call any lexical procedure > visible from the special form. It gets even worse when you consider > first-class continuations. We are calling the Lilypond parser in that "top-level form". The optimizer is not much of a worry. > I believe that this is the wrong approach, though it may be worth > considering for the sake of allowing Lilypond to continue using its > existing implementation strategy. Uh, we are not talking about "implementation strategy" but language features. > In general, the _right_ way to build a custom extension language using > Guile 2 is to write a compiler that converts your language into one of > the other languages that Guile 2 supports. Lilypond is not Scheme. It has syntax ambiguities that are resolved by lexical tie-ins and thus depend on the context. You can't easily compile it in advance. And you are _totally_ putting the cart before the horse here. Lilypond is not supposed to be an extension language for Guile, but Guile is supposed to be an extension language for Lilypond. The acronym Guile stands for "GNU's Ubiquitous Intelligent Language for Extension". You are losing sight of what Guile is supposed to be. As an extension language, it does not make sense that it dictates the lexical and the program structure of the system it is supposed to be extending. > If there's something about Lilypond's language that you believe would > make compilation impractical, let's talk about it. Its syntax and semantics. <URL:http://git.savannah.gnu.org/cgit/lilypond.git/tree/lily/parser.yy> If I call a function with an optional argument of type integer? before an argument of type ly:music? and I encounter #x, then the value of x decides whether this argument will be used as the optional argument or as the following argument. The rest of the parsing has to follow. > Maybe the Guile experts on this list can find a clever solution, or > else maybe we can enhance Guile to support Lilypond's language in a > straightforward manner. > > I would be glad to help with this. In the long run, it might be less > work for us Guile hackers to implement a nice compiler for Lilypond than > to implement and forever maintain the "capture-lexical-environment" > special form, and it would almost certainly have better results. You are working from the premise that Guile should govern the architecture of the system it is supposed to be extending. That Lilypond is one of the few serious systems actually using Guile as an extension language does not make it a good idea to turn it into a system that Guile uses as its extension. The hard way. With lots of work. You can't expect that kind of investment to be done for every application that considers using Guile. -- David Kastrup ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-12 6:47 ` David Kastrup @ 2011-12-12 18:29 ` Mark H Weaver 2011-12-12 19:56 ` David Kastrup 2011-12-12 21:50 ` Andy Wingo 0 siblings, 2 replies; 82+ messages in thread From: Mark H Weaver @ 2011-12-12 18:29 UTC (permalink / raw) To: David Kastrup; +Cc: guile-devel David Kastrup <dak@gnu.org> writes: >> In general, the _right_ way to build a custom extension language using >> Guile 2 is to write a compiler that converts your language into one of >> the other languages that Guile 2 supports. > > Lilypond is not Scheme. It has syntax ambiguities that are resolved by > lexical tie-ins and thus depend on the context. You can't easily > compile it in advance. Lexical tie-ins require the use of a context-sensitive parser, but how does it prevent compilation in advance? Guile 2 places no constraints whatsoever on the parser used to compile your language to Guile. You could use the exact same Bison parser you are currently using, but with different actions. >> If there's something about Lilypond's language that you believe would >> make compilation impractical, let's talk about it. > > Its syntax and semantics. > > <URL:http://git.savannah.gnu.org/cgit/lilypond.git/tree/lily/parser.yy> > > If I call a function with an optional argument of type integer? before > an argument of type ly:music? and I encounter #x, then the value of x > decides whether this argument will be used as the optional argument or > as the following argument. The rest of the parsing has to follow. I don't see a serious problem here. In general, anything that can't be done at compile time can be postponed to runtime easily enough. To address the specific example you give above, the compiled Lilypond procedure could look something like this: (define (myproc . args) (extract-lyargs args `((#:optional ,integer?) (#:required ,ly:music?)) (lambda (x music) body ...))) where `extract-lyargs' is a procedure (part of the runtime environment) that takes the list of arguments and a formal-parameter specification, and does the runtime tests needed to decide how the arguments should be put into `x' and `music'. > And you are _totally_ putting the cart before the horse here. Lilypond > is not supposed to be an extension language for Guile, but Guile is > supposed to be an extension language for Lilypond. The acronym Guile > stands for "GNU's Ubiquitous Intelligent Language for Extension". You > are losing sight of what Guile is supposed to be. I don't know about that. You seem to imply that Lilypond's use of Guile is very typical, and that other programs that use Guile for extension will run into similar difficulties, but as far as I can tell Lilypond is quite unique here. Typically, an application using libguile (or any other language library) allows the library to handle all aspects of parsing and running the supported extension language(s). In Guile's case, the idea is that whenever a new language is added to Guile, applications using libguile can automatically make use of those new languages. You are using Guile in a very unusual way. You have constructed a hybrid language of both Scheme and Lilypond, where each can be nested within the other (so far so good), but -- and here's the kicker -- you apparently want to implement this hybrid language using two separate interpreters maintained by two separate groups that are each able to run code within lexical environments established by the other one. This is a fundamentally bad idea, because this structure makes it impossible for either of these language implementations to evolve in any significant way. It forces them both to remain simple interpreters. > You are working from the premise that Guile should govern the > architecture of the system it is supposed to be extending. I can understand why it appears that way to you, but this is only because you have built a system on Guile that places unreasonable constraints upon the internal workings of Guile. Please try to look at it from our perspective, and also from the perspective of other programs that use Guile in a more typical way. Most users of Guile 2 benefit from the architectural and efficiency improvements, and are not harmed by them. We are not trying to impose any particular architecture on your system, only on the way the language implementation itself works. Is it really so unreasonable that the language implementation should be under Guile's control? Is this really a betrayal of the original vision of what Guile is "supposed to be", as you wrote above? If you think so, can you please back this up with some references? Mark ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-12 18:29 ` Mark H Weaver @ 2011-12-12 19:56 ` David Kastrup 2011-12-12 20:39 ` rixed 2011-12-12 21:40 ` Mark H Weaver 2011-12-12 21:50 ` Andy Wingo 1 sibling, 2 replies; 82+ messages in thread From: David Kastrup @ 2011-12-12 19:56 UTC (permalink / raw) To: Mark H Weaver; +Cc: guile-devel Mark H Weaver <mhw@netris.org> writes: > David Kastrup <dak@gnu.org> writes: >>> In general, the _right_ way to build a custom extension language using >>> Guile 2 is to write a compiler that converts your language into one of >>> the other languages that Guile 2 supports. >> >> Lilypond is not Scheme. It has syntax ambiguities that are resolved by >> lexical tie-ins and thus depend on the context. You can't easily >> compile it in advance. > > Lexical tie-ins require the use of a context-sensitive parser, but how > does it prevent compilation in advance? Guile 2 places no constraints > whatsoever on the parser used to compile your language to Guile. You > could use the exact same Bison parser you are currently using, but with > different actions. > >>> If there's something about Lilypond's language that you believe would >>> make compilation impractical, let's talk about it. >> >> Its syntax and semantics. >> >> <URL:http://git.savannah.gnu.org/cgit/lilypond.git/tree/lily/parser.yy> >> >> If I call a function with an optional argument of type integer? before >> an argument of type ly:music? and I encounter #x, then the value of x >> decides whether this argument will be used as the optional argument or >> as the following argument. The rest of the parsing has to follow. > > I don't see a serious problem here. In general, anything that can't be > done at compile time can be postponed to runtime easily enough. We are running in circles here. The problem is that I don't get to keep the lexical environment from compile time for use at runtime. > To address the specific example you give above, the compiled Lilypond > procedure could look something like this: You are putting me on, right? I explain why Lilypond is not compiled, and you talk about a "compiled Lilypond procedure". > (define (myproc . args) > (extract-lyargs args `((#:optional ,integer?) (#:required ,ly:music?)) > (lambda (x music) > body ...))) > > where `extract-lyargs' is a procedure (part of the runtime environment) > that takes the list of arguments and a formal-parameter specification, > and does the runtime tests needed to decide how the arguments should be > put into `x' and `music'. Very funny. If x is not an integer, it is put into the music argument instead, and the next "argument" is not an argument but independent code. >> And you are _totally_ putting the cart before the horse here. >> Lilypond is not supposed to be an extension language for Guile, but >> Guile is supposed to be an extension language for Lilypond. The >> acronym Guile stands for "GNU's Ubiquitous Intelligent Language for >> Extension". You are losing sight of what Guile is supposed to be. > > I don't know about that. You seem to imply that Lilypond's use of > Guile is very typical, Not at all. I imply that Lilypond's use corresponds to what Guile is advertised as being useful for. Not all that many people bother using it in that way, and Guile 2 is taking a definite step backward with regard to being useful for it. The "Guile 2 migration project" for Lilypond is solidly running into man-months of work with no end in sight. I have been working around killing capturable lexical environments (quite more important for an extension language than capturing continuations), but it is not like this is the only problem. > and that other programs that use Guile for extension will run into > similar difficulties, but as far as I can tell Lilypond is quite > unique here. Because nobody else uses Guile for serious extensions. And not because of its performance: that is _irrelevant_ for most extension purposes. The performance angle is interesting when one uses Guile as a general purpose _programming_ language. You are sacrificing your target clientele here. > Typically, an application using libguile (or any other language > library) allows the library to handle all aspects of parsing and > running the supported extension language(s). In Guile's case, the > idea is that whenever a new language is added to Guile, applications > using libguile can automatically make use of those new languages. > > You are using Guile in a very unusual way. You have constructed a > hybrid language of both Scheme and Lilypond, That's what "extension language" as opposed to "implementation language" means. > where each can be nested within the other (so far so good), but -- and > here's the kicker -- you apparently want to implement this hybrid > language using two separate interpreters maintained by two separate > groups that are each able to run code within lexical environments > established by the other one. Not really. We run Guile code inside of lexical environments established by Guile code, just with a difference in timing. That is exactly the same thing as Guile does when using macros. But there is no point in turning everything into macros (since we then need to run primitive-eval for everything), and actually Guile v2 _also_ throws a wrench into using macros (Ian Hulin has been working on migrating the use of macros to Guile v2 and it has not exactly been fun and smooth sailing up to now). > This is a fundamentally bad idea, because this structure makes it > impossible for either of these language implementations to evolve in > any significant way. It forces them both to remain simple > interpreters. We are talking about the syntactic front end here. A miniscule amount of the runtime is actually spent in it. You are arguing for turning a convenient user environment for processing an input language that can make good and logical use of Scheme into a complex compiled mess for the sake of hypothetical performance gains. This is O(n) for n being the size of the input. It is irrelevant for the performance of the system. >> You are working from the premise that Guile should govern the >> architecture of the system it is supposed to be extending. > > I can understand why it appears that way to you, but this is only > because you have built a system on Guile that places unreasonable > constraints upon the internal workings of Guile. Again, you are putting the cart before the horse. If an "extension language" dictates the structure and syntax of the system it is supposed to extend, it is no longer doing the job of an extension. > Please try to look at it from our perspective, and also from the > perspective of other programs that use Guile in a more typical way. Name a few other programs that use Guile as an _extension_ language rather than as a _programming_ language. > Most users of Guile 2 benefit from the architectural and efficiency > improvements, and are not harmed by them. You are trying to compete with systems like Chicken, Stalin and C rather than Tcl, JavaScript and Lua. But losing by a smaller margin in their market niche is not going to buy you anything in exchange for ignoring the needs of your existing users. So you say we should not be using Guile anymore if we intend to extend the functionality of Lilypond in a manner where Guile and Lilypond play smooth and predictably hand in hand. -- David Kastrup ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-12 19:56 ` David Kastrup @ 2011-12-12 20:39 ` rixed 2011-12-12 21:02 ` David Kastrup 2011-12-12 21:40 ` Mark H Weaver 1 sibling, 1 reply; 82+ messages in thread From: rixed @ 2011-12-12 20:39 UTC (permalink / raw) To: guile-devel > > and that other programs that use Guile for extension will run into > > similar difficulties, but as far as I can tell Lilypond is quite > > unique here. > > Because nobody else uses Guile for serious extensions. And not because > of its performance: that is _irrelevant_ for most extension purposes. > The performance angle is interesting when one uses Guile as a general > purpose _programming_ language. You are sacrificing your target > clientele here. Obviously Guile does not suffer from too many users right now, but stating that Lilypond is the only project using Guile seriously seams a little excessive. Be confident that I'm ashamed by my ignorance but I do not know how exactly Lilypond uses Guile (nor what Lilypond exactly does), but your description of it does sound like it's the only way to "extend" a program. You seams to view an extension language as a tool to extend a language, while many projects use guile merely to extend a program. So let me present you the view of the average Joe Schemer with regard to Guile as an extension language: For me, extending a program (supposedly written in some kind of low level language, let's say some kind of C) with Guile or any other higher level language, is the action of linking together the low level stuff and libguile in a way that some scheme programs can be run with easy access to the inner functionalities of the program, so that : - the poor maintainers of the old C-like program are not forced to code every trivia in C-like language - the poor users are given an easier way to configure and even program the old C dinosaur - if time permits, evolve from extending the C with Scheme to extending the Scheme with C, so that not only the C program can access all scheme facilities and libraries, but also all other programs can use the internal of the old C program (this being only possible if you have only one extension language) In that regard, having Guile being compiled to bytecode, to native, interpreted, JITed or whatever is irrelevant. Indeed, it's also quite irrelevant if it's scheme or anything else, but : - the more expressiveness we have the better - the faster the better - the more available front-end languages, the less frightened the users will be (if not less parentheses then at least at there usual locations...) In my experience these two elements played a great role in the acceptance of Guile (over Lua, mainly) as the extension language. I don't pretend you were wrong to use Guile as you did of course! My 2c, which is not a lot per line, just to let you know that some projects are indeed enjoying their migration from guile 1.8 to guile 2. ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-12 20:39 ` rixed @ 2011-12-12 21:02 ` David Kastrup 2011-12-12 21:58 ` Mark H Weaver 0 siblings, 1 reply; 82+ messages in thread From: David Kastrup @ 2011-12-12 21:02 UTC (permalink / raw) To: guile-devel rixed@happyleptic.org writes: >> > and that other programs that use Guile for extension will run into >> > similar difficulties, but as far as I can tell Lilypond is quite >> > unique here. >> >> Because nobody else uses Guile for serious extensions. And not because >> of its performance: that is _irrelevant_ for most extension purposes. >> The performance angle is interesting when one uses Guile as a general >> purpose _programming_ language. You are sacrificing your target >> clientele here. > > Obviously Guile does not suffer from too many users right now, but > stating that Lilypond is the only project using Guile seriously seams > a little excessive. _As_ _an_ _extension_ _language_ rather than as a mostly independent subsystem. > Be confident that I'm ashamed by my ignorance but I do not know how > exactly Lilypond uses Guile (nor what Lilypond exactly does), but your > description of it does sound like it's the only way to "extend" a > program. Not at all. But when we are talking about an _extension_ _language_, the implication is that it works in bits and pieces where it is convenient. That it _integrates_ with a larger system. Lexical environments are a fundamental part of what integration may involve, and they operate at a different level as modules. Macros play _into_ lexical environments, so obviously Scheme itself recognizes the importance of being able to extend. -- David Kastrup ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-12 21:02 ` David Kastrup @ 2011-12-12 21:58 ` Mark H Weaver 0 siblings, 0 replies; 82+ messages in thread From: Mark H Weaver @ 2011-12-12 21:58 UTC (permalink / raw) To: David Kastrup; +Cc: guile-devel David Kastrup <dak@gnu.org> writes: >> Be confident that I'm ashamed by my ignorance but I do not know how >> exactly Lilypond uses Guile (nor what Lilypond exactly does), but your >> description of it does sound like it's the only way to "extend" a >> program. > > Not at all. But when we are talking about an _extension_ _language_, > the implication is that it works in bits and pieces where it is > convenient. That it _integrates_ with a larger system. Yes, extension languages are meant to integrate into a larger _program_, that much we can agree on. However, I disagree that "extension languages" are, by definition, meant to integrate into an external _language_ _implementation_. > Lexical environments are a fundamental part of what integration may > involve, and they operate at a different level as modules. Macros > play _into_ lexical environments, so obviously Scheme itself > recognizes the importance of being able to extend. Yes, Scheme recognizes the importance of being able to extend the language, but only within the framework of a single low-level language _implementation_. This is a separate issue from being able to extend a program using an "extension language". Mark ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-12 19:56 ` David Kastrup 2011-12-12 20:39 ` rixed @ 2011-12-12 21:40 ` Mark H Weaver 1 sibling, 0 replies; 82+ messages in thread From: Mark H Weaver @ 2011-12-12 21:40 UTC (permalink / raw) To: David Kastrup; +Cc: guile-devel David Kastrup <dak@gnu.org> writes: > Very funny. If x is not an integer, it is put into the music argument > instead, and the next "argument" is not an argument but independent > code. Ah, okay. In that case, the design of the Lilypond language does indeed make compilation (or even parsing) before execution absolutely impossible. Oh well. >> You are using Guile in a very unusual way. You have constructed a >> hybrid language of both Scheme and Lilypond, > > That's what "extension language" as opposed to "implementation language" > means. I guess this is the crux of your misunderstanding over what Guile is "supposed to be". Apparently you believe that an "extension language" is one that allows you to mix your own language interpreter with the extension language's interpreter to build a hybrid language that is implemented by both code bases working together. Can you please find a reference to back up your definition of "extension language"? I looked and I couldn't find one that supports your view. For the record, I don't believe that this use of Guile was _ever_ what Guile was intended for. Anyway, it's water under the bridge now. To be fair, some past maintainer of Guile apparently decided to make "procedure-environment" part of the public API -- a very serious error in my view -- and the Lilypond developers made use of this functionality to build their system. I guess the responsibility to make this right ultimately rests on our shoulders, as inheritors of the unwise promises made by our predecessors. In order to make this right, and in general to support languages like Lilypond that cannot even be parsed before execution, I guess we should implement the "capture-lexical-environment" special form, along with a "local-eval" that makes use of it. I guess the most straightforward implementation is to simply inhibit compilation of any top-level form that contains a "capture-lexical-environment" special form. Instead we would use the bootstrap evaluator to execute such top-level forms, and also to implement "local-eval". Andy, this is your area. I'd be willing to work on this, but before I do, can you at least comment on whether you think this is the right approach, and what complications I'm likely to run into along the way? Thanks, Mark ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-12 18:29 ` Mark H Weaver 2011-12-12 19:56 ` David Kastrup @ 2011-12-12 21:50 ` Andy Wingo 2011-12-13 9:02 ` David Kastrup ` (2 more replies) 1 sibling, 3 replies; 82+ messages in thread From: Andy Wingo @ 2011-12-12 21:50 UTC (permalink / raw) To: Mark H Weaver; +Cc: David Kastrup, guile-devel On Mon 12 Dec 2011 19:29, Mark H Weaver <mhw@netris.org> writes: > You are using Guile in a very unusual way. You have constructed a > hybrid language of both Scheme and Lilypond, where each can be nested > within the other (so far so good), but -- and here's the kicker -- you > apparently want to implement this hybrid language using two separate > interpreters maintained by two separate groups that are each able to run > code within lexical environments established by the other one. > > This is a fundamentally bad idea, because this structure makes it > impossible for either of these language implementations to evolve in any > significant way. It forces them both to remain simple interpreters. FWIW Mark I agree with you. But, in the event that David wants to continue with his current strategy, there are other things that can be done. David, did you know that Guile's evaluator is implemented in Scheme? That means that if you want an evaluator with different semantics -- for example, something closer to Kernel[0], as David appears to want -- then you can implement an evaluator that provides for fexprs and the like, and it will run about as well as Guile's evaluator. Did you see my implementation of `local-eval' [1]? It leverages (hah!) Guile's macro expander, but otherwise is a straightforward interpreter. If you find it slow, there are some simple, classic optimizations that can be made. With some work, it could present a similar interface to 1.8's `local-eval', `procedure-environment', `the-environment', and such things. [0] http://web.cs.wpi.edu/~jshutt/kernel.html; a lovely language. Terrifies me, as an implementor. Looks like a user's delight though. [1] http://lists.gnu.org/archive/html/guile-user/2011-02/msg00032.html Regards, Andy -- http://wingolog.org/ ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-12 21:50 ` Andy Wingo @ 2011-12-13 9:02 ` David Kastrup 2011-12-13 13:05 ` Andy Wingo 2011-12-13 11:14 ` David Kastrup 2011-12-14 13:52 ` Ludovic Courtès 2 siblings, 1 reply; 82+ messages in thread From: David Kastrup @ 2011-12-13 9:02 UTC (permalink / raw) To: guile-devel Andy Wingo <wingo@pobox.com> writes: > On Mon 12 Dec 2011 19:29, Mark H Weaver <mhw@netris.org> writes: > >> You are using Guile in a very unusual way. You have constructed a >> hybrid language of both Scheme and Lilypond, where each can be nested >> within the other (so far so good), but -- and here's the kicker -- you >> apparently want to implement this hybrid language using two separate >> interpreters maintained by two separate groups that are each able to run >> code within lexical environments established by the other one. >> >> This is a fundamentally bad idea, because this structure makes it >> impossible for either of these language implementations to evolve in any >> significant way. It forces them both to remain simple interpreters. > > FWIW Mark I agree with you. > > But, in the event that David wants to continue with his current > strategy, Uh, reality check. Lilypond's input language is not "David's current strategy". > there are other things that can be done. David, did you know that > Guile's evaluator is implemented in Scheme? That means that if you > want an evaluator with different semantics -- for example, something > closer to Kernel[0], as David appears to want -- then you can > implement an evaluator that provides for fexprs and the like, and it > will run about as well as Guile's evaluator. > > Did you see my implementation of `local-eval' [1]? It leverages (hah!) > Guile's macro expander, but otherwise is a straightforward > interpreter. It does not help because it requires _advance_ knowledge of when you are going to want to fish for environments. You can call Lilypond's #{ ... #} construct in the normal REPL. You can call it in any function definition. It is pervasive. With Lilypond, I can always wrap stuff requiring closures into (lambda ()) preventively at compile time, and execute them on an as-needed base at runtime. And that's actually what we do. But there is no way to ask Guile when it would be needed since Guile does not hand out the information about bound variables in a given lexical scope. To get at that information, we would have to shadow every binding form in Guile. > If you find it slow, there are some simple, classic optimizations that > can be made. With some work, it could present a similar interface to > 1.8's `local-eval', `procedure-environment', `the-environment', and > such things. When we are forced to reimplement a toy Scheme interpreter from scratch anyway, including implementing every single defining form, one really has to ask oneself why pick Guile in the first place if it does not give you a Scheme language to work with? Just for the API? -- David Kastrup ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-13 9:02 ` David Kastrup @ 2011-12-13 13:05 ` Andy Wingo 2011-12-13 13:56 ` David Kastrup 0 siblings, 1 reply; 82+ messages in thread From: Andy Wingo @ 2011-12-13 13:05 UTC (permalink / raw) To: David Kastrup; +Cc: guile-devel On Tue 13 Dec 2011 10:02, David Kastrup <dak@gnu.org> writes: > Lilypond's input language is not "David's current strategy". I was referring to your implementation strategy. Mark describes another implementation strategy. > It does not help because it requires _advance_ knowledge of when you are > going to want to fish for environments. You can call Lilypond's > #{ ... #} construct in the normal REPL. You can call it in any function > definition. It is pervasive. I was suggesting to evaluate all lilypond "scheme" code with the lilypond "scheme" interpreter. That would make `the-environment' available everywhere. I still think making your #{}# parser expand to lexically-scoped Scheme is the best option. Another option is to use the reflective facilities to implement a form of procedure-environment. If you compile your Scheme procedures, with partial evaluation disabled, you should be able to use program-bindings to get this information. I wonder if we could provide some sort of current-bindings syntactic form, also. It would require psyntax hooks, but it could work. Andy -- http://wingolog.org/ ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-13 13:05 ` Andy Wingo @ 2011-12-13 13:56 ` David Kastrup 2011-12-13 14:34 ` Andy Wingo 0 siblings, 1 reply; 82+ messages in thread From: David Kastrup @ 2011-12-13 13:56 UTC (permalink / raw) To: guile-devel Andy Wingo <wingo@pobox.com> writes: > On Tue 13 Dec 2011 10:02, David Kastrup <dak@gnu.org> writes: > >> Lilypond's input language is not "David's current strategy". > > I was referring to your implementation strategy. It's not a strategy. Merely the least painful way to do things at a given point of time. The given point of time is that we need to cater for both Guile v1 and v2. > Mark describes another implementation strategy. > >> It does not help because it requires _advance_ knowledge of when you are >> going to want to fish for environments. You can call Lilypond's >> #{ ... #} construct in the normal REPL. You can call it in any function >> definition. It is pervasive. > > I was suggesting to evaluate all lilypond "scheme" code with the > lilypond "scheme" interpreter. Which means exactly that we can't use the repl anymore, and likely also not the Guile debugger. And get worse performance than with Guile v1. > I still think making your #{}# parser expand to lexically-scoped > Scheme is the best option. If using Scheme as one humongous block is the best (and pretty much only) option, Guile is no longer an extension language but a platform. And that means letting down the admittedly few people who bought into Guile's previous agenda. In any case, I don't see what is supposed to make Guile v2 conceptually distinct from any old Scheme interpreter if its closures are closed and shut. > Another option is to use the reflective facilities to implement a form > of procedure-environment. If you compile your Scheme procedures, with > partial evaluation disabled, you should be able to use > program-bindings to get this information. program-bindings does not appear present in Guile 1.8, so that's shelved at the current point of time. And the documentation in the manual about "Compiled Procedures" states Compiled procedures, also known as programs, respond all procedures that operate on procedures. In addition, there are a few more accessors for low-level details on programs. which likely could be phrased a bit more clearly if one wanted to find out how to make use of it. It then goes on to state: — Scheme Procedure: program-bindings program — Scheme Procedure: make-binding name boxed? index start end — Scheme Procedure: binding:name binding — Scheme Procedure: binding:boxed? binding — Scheme Procedure: binding:index binding — Scheme Procedure: binding:start binding — Scheme Procedure: binding:end binding Bindings annotations for programs, along with their accessors. Bindings declare names and liveness extents for block-local variables. The best way to see what these are is to play around with them at a REPL. See VM Concepts, for more information. Sorry, but "try out and see what it does" is not exactly a guarantee for and/or a definition of a stable API. I was not all that surprised to find that the chapter "VM Concepts" does indeed contain more information, unfortunately information that is not in any obvious way related to program-bindings. With that kind of documentation, few people will actually be using these functions and as a result you will likely not feel any compulsion to keep those around, either. > I wonder if we could provide some sort of current-bindings syntactic > form, also. It would require psyntax hooks, but it could work. I can't help the impression that a dependable and documented long-term strategy and corresponding commitment would at the current point of time be more important than brain-storming about short-lived hacks. The distinguishing feature for Lisp-like systems is the degree to which it is self-descriptive and "live". _That_ (and certainly not its syntax or performance) is what has made this language family the prime platform for AI applications. -- David Kastrup ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-13 13:56 ` David Kastrup @ 2011-12-13 14:34 ` Andy Wingo 2011-12-13 15:27 ` David Kastrup 0 siblings, 1 reply; 82+ messages in thread From: Andy Wingo @ 2011-12-13 14:34 UTC (permalink / raw) To: David Kastrup; +Cc: guile-devel Hello David, Let us focus on solutions. If the this realm does have a coin, it is good-will. All participants start with ample deposits, but yours is draining fast. Please listen to what people are saying; they are trying to help you. Specifically: On Tue 13 Dec 2011 14:56, David Kastrup <dak@gnu.org> writes: >> I wonder if we could provide some sort of current-bindings syntactic >> form, also. It would require psyntax hooks, but it could work. > > I can't help the impression that a dependable and documented long-term > strategy and corresponding commitment would at the current point of time > be more important than brain-storming about short-lived hacks. Um... what? It sounds like `current-bindings' is the thing you need. But, um... I guess what I want to say is that you are making it pretty hard to work with you. Regards, Andy -- http://wingolog.org/ ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-13 14:34 ` Andy Wingo @ 2011-12-13 15:27 ` David Kastrup 2011-12-13 15:48 ` Andy Wingo 2011-12-13 15:52 ` David Kastrup 0 siblings, 2 replies; 82+ messages in thread From: David Kastrup @ 2011-12-13 15:27 UTC (permalink / raw) To: Andy Wingo; +Cc: guile-devel Andy Wingo <wingo@pobox.com> writes: > Hello David, > > Let us focus on solutions. > > If the this realm does have a coin, it is good-will. All participants > start with ample deposits, but yours is draining fast. Please listen to > what people are saying; they are trying to help you. Lilypond already has an ugly inefficient hack in it that will keep it working in regard of the closure department largely independent of whatever Guile development chooses to come up with next. > On Tue 13 Dec 2011 14:56, David Kastrup <dak@gnu.org> writes: > >>> I wonder if we could provide some sort of current-bindings syntactic >>> form, also. It would require psyntax hooks, but it could work. >> >> I can't help the impression that a dependable and documented >> long-term strategy and corresponding commitment would at the current >> point of time be more important than brain-storming about short-lived >> hacks. > > Um... what? It sounds like `current-bindings' is the thing you need. It will at least be a year before any solution that does not work with Guile 1.8 will be accepted into Lilypond. Any effort spent on something that is not likely to survive that long because nobody will have ever used it by the time Lilypond would bother picking it up, is going to be wasted. And additional cause for bad blood. And since we are not talking about time-critical code paths in Lilypond, we'll be able to kludge around the consequences of Guile sacrificing its introspective qualities. I am not worried about Lilypond. I can work with any size of crowbar. I am worried about Guile. > But, um... I guess what I want to say is that you are making it > pretty hard to work with you. Since we are working on different projects, that is not a problem. Take a look at <URL:http://www.gnu.org/s/guile/>. It states: Successful and long-lived examples of Free Software projects that use Guile are TeXmacs, LilyPond, and GnuCash. If you take a look at <URL:http://www.texmacs.org/tmweb/about/todo.en.html>, you'll find 3.Scheme interface 3.1.General Scheme plug-in Internally present Guile as a plug-in, which could later be replaced by another Scheme implementation. in their list of things to do. For GNUCash, you have <URL:http://wiki.gnucash.org/wiki/Roadmap#Scheme_minimization> Scheme minimization There are some parts of GnuCash that make a round-trip into scheme code for no really good reason. The developers would like to throw out those parts of Scheme. Reports Right now reports are scheme scripts that programatically generate HTML. Scheme is impenetrable to most programmers. Expecting users to be able to write reports in Scheme is completely unreasonable. The ideal solution should have three modules: Record selection: Ideally graphical with an SQL-like "advanced" option Layout: The original item here suggested an HTML template; that could be the "advanced" option, with a simple table/graph being the default Style: CSS. It's built into WebKit, we should use it. Javascript: WebKit supports javascript so complicated interactivity can be added. Example: ability to expand/collapse levels of the account hierarchy in a report. This could be extended with Javascript interfaces to the API so that all of the report code is written in Javascript instead of Scheme. Any report module will still need some sort of scripting language to "calculate the numbers". Currently we have Scheme for this, but the developers would like to get away from that. Python might be a better option. So of your three listed showcase applications, the two others are trying to get away from Guile and/or Scheme. And it does not look like performance is the reason. So I don't think that throwing out _distinguishing_ selling points of Guile is necessarily doing you a favor. And the transparency with which it integrates with its language environment and the fact that one can continue to use its evaluator and debugger even for the application for which it serves as an extension language, certainly is a selling point. Even if documentation and commitment to interfaces are not doing their share. There is a reason that pure Scheme compilers like Chicken and Stalin don't see widespread employment, and that reason is that the introspective and interactive character of the Lisp-like languages is lost to a good degree, and the performance gain does not in itself suffice to compete with classical compiled languages and their usually more human-readable code and concepts. -- David Kastrup ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-13 15:27 ` David Kastrup @ 2011-12-13 15:48 ` Andy Wingo 2011-12-13 16:08 ` David Kastrup 2011-12-13 16:24 ` David Kastrup 2011-12-13 15:52 ` David Kastrup 1 sibling, 2 replies; 82+ messages in thread From: Andy Wingo @ 2011-12-13 15:48 UTC (permalink / raw) To: David Kastrup; +Cc: guile-devel On Tue 13 Dec 2011 16:27, David Kastrup <dak@gnu.org> writes: >> It sounds like `current-bindings' is the thing you need. > > It will at least be a year before any solution that does not work with > Guile 1.8 will be accepted into Lilypond. It is possible to have similar interfaces with different implementations, using `cond-expand'. lily.scm does this in one case, implementing 2.0 interfaces on 1.8. I'll take a look at implementing something like this. To summarize your issue: you have code like: (lambda (a b c) #{ here I have custom code that references lexical variables; should it be able to set them too? }#) It would be relatively easy to pass in an alist of the lexicals, for reference purposes; but do you want to be able to set them too, from within that EDSL? Andy -- http://wingolog.org/ ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-13 15:48 ` Andy Wingo @ 2011-12-13 16:08 ` David Kastrup 2011-12-13 16:27 ` Andy Wingo 2011-12-13 16:24 ` David Kastrup 1 sibling, 1 reply; 82+ messages in thread From: David Kastrup @ 2011-12-13 16:08 UTC (permalink / raw) To: guile-devel Andy Wingo <wingo@pobox.com> writes: > On Tue 13 Dec 2011 16:27, David Kastrup <dak@gnu.org> writes: > >>> It sounds like `current-bindings' is the thing you need. >> >> It will at least be a year before any solution that does not work with >> Guile 1.8 will be accepted into Lilypond. > > It is possible to have similar interfaces with different > implementations, using `cond-expand'. lily.scm does this in one case, > implementing 2.0 interfaces on 1.8. > > I'll take a look at implementing something like this. > > To summarize your issue: you have code like: > > (lambda (a b c) > #{ here I have custom code that references lexical variables; > should it be able to set them too? }#) > > It would be relatively easy to pass in an alist of the lexicals, for > reference purposes; but do you want to be able to set them too, from > within that EDSL? The current implementation wraps scraps of code into (lambda () ...) and executes them on-demand. So the expectation is that embedded Scheme code can have side-effects on the lexical environment like with (let ((xxx 2)) #{ #(set! xxx (1+ xxx)) #}) while something like (let ((xxx 2)) #{ xxx = "xx" #}) is not at the current point of time expected to work. In fact, LilyPond itself never accesses the lexical environment (or its simulation): the environment is only made available to embedded Scheme. It is basically a black box, Scheme to Scheme. Lilypond only uses the current module for reading and writing variables. -- David Kastrup ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-13 16:08 ` David Kastrup @ 2011-12-13 16:27 ` Andy Wingo 2011-12-13 16:54 ` David Kastrup 2011-12-13 17:28 ` Mark H Weaver 0 siblings, 2 replies; 82+ messages in thread From: Andy Wingo @ 2011-12-13 16:27 UTC (permalink / raw) To: David Kastrup; +Cc: guile-devel On Tue 13 Dec 2011 17:08, David Kastrup <dak@gnu.org> writes: > The current implementation wraps scraps of code into (lambda () ...) and > executes them on-demand. So the expectation is that embedded Scheme > code can have side-effects on the lexical environment like with > > (let ((xxx 2)) > #{ #(set! xxx (1+ xxx)) #}) This closure strategy sounds fine, no? It's what I would do, I think, if I understand the problem correctly. I thought that you were saying that lilypond code could reference and set Scheme lexical variables. I was also under the impression that lilypond code could define lexical variables. If neither of these are true, then closures sound like a fine solution to me. Am I missing something? It has been a long thread :) Andy -- http://wingolog.org/ ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-13 16:27 ` Andy Wingo @ 2011-12-13 16:54 ` David Kastrup 2011-12-13 18:58 ` Andy Wingo 2011-12-13 17:28 ` Mark H Weaver 1 sibling, 1 reply; 82+ messages in thread From: David Kastrup @ 2011-12-13 16:54 UTC (permalink / raw) To: guile-devel Andy Wingo <wingo@pobox.com> writes: > On Tue 13 Dec 2011 17:08, David Kastrup <dak@gnu.org> writes: > >> The current implementation wraps scraps of code into (lambda () ...) and >> executes them on-demand. So the expectation is that embedded Scheme >> code can have side-effects on the lexical environment like with >> >> (let ((xxx 2)) >> #{ #(set! xxx (1+ xxx)) #}) > > This closure strategy sounds fine, no? It's what I would do, I think, > if I understand the problem correctly. > > I thought that you were saying that lilypond code could reference and > set Scheme lexical variables. This thread started because I was thinking about creating Lilypond structures that could be defined via procedures with setters. In that case, doing the equivalent of (set! (lambda () (vref x 4)) 3) would not work out all that well. But that would likely be utopical anyway. > I was also under the impression that lilypond code could define > lexical variables. How would it do that without an API? > If neither of these are true, then closures sound like a fine solution > to me. > > Am I missing something? Performance, space, simplicity, robustness. Compiling five closures that do nothing except accessing a single variable each is a bit wasteful. We keep having bug reports about bad stuff happening with #xxx and $xxx constructs in Lilypond _comments_ inside of embedded Lilypond: since the preliminary wrapping of $x and #x into lambdas does not recognize the lexical structure of the embedded Lilypond passage, and one can't let the lexer run in advance since the lexer is mode-dependent and thus needs the parser to run in sync. I am not saying that the current solution is unworkable. But it does nothing to recommend Guile in particular, while the solution using procedure-environment did. Even though taking the procedure-environment of an arbitrary lambda function was quite more hackish (and less likely to survive an optimizing compiler) than a special form for capturing the current environment would have been. But it was more like a master key than a crowbar. -- David Kastrup ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-13 16:54 ` David Kastrup @ 2011-12-13 18:58 ` Andy Wingo 2011-12-13 22:23 ` David Kastrup 0 siblings, 1 reply; 82+ messages in thread From: Andy Wingo @ 2011-12-13 18:58 UTC (permalink / raw) To: David Kastrup; +Cc: guile-devel On Tue 13 Dec 2011 17:54, David Kastrup <dak@gnu.org> writes: >> Am I missing something? > > Performance, space, simplicity, robustness. Compiling five closures > that do nothing except accessing a single variable each is a bit > wasteful. Sure. Let me see if I finally understand the issue here: You have a function: (define-music-function (foo bar) (ly:something? bar) #{ /la /la /la $bar $bar $bar #(scheme-expression!) /ok }#) Before, you could turn the #{}# into a lambda and get at the $vars and evaluate the #(expressions) in the procedure-environment of the lambda. Now, you have to munge around in the expression and, in this case, produce 4 closures: (lambda () bar), 3 times, and (lambda () (scheme-expression!)). Is that right? Andy -- http://wingolog.org/ ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-13 18:58 ` Andy Wingo @ 2011-12-13 22:23 ` David Kastrup 0 siblings, 0 replies; 82+ messages in thread From: David Kastrup @ 2011-12-13 22:23 UTC (permalink / raw) To: Andy Wingo; +Cc: guile-devel Andy Wingo <wingo@pobox.com> writes: > On Tue 13 Dec 2011 17:54, David Kastrup <dak@gnu.org> writes: > >>> Am I missing something? >> >> Performance, space, simplicity, robustness. Compiling five closures >> that do nothing except accessing a single variable each is a bit >> wasteful. > > Sure. > > Let me see if I finally understand the issue here: > > You have a function: > > (define-music-function (foo bar) > (ly:something? bar) ^^^^^^^^^^^^^^^^^^^ Those are predicates, not arguments. > #{ /la /la /la > $bar $bar $bar > #(scheme-expression!) Both # and $ come before Scheme expressions. # is the regular type visible in the parser, and has, for example, the property that it will always serve as exactly one argument of a music function. $ is an "immediate" type that gets camouflaged as a Lilypond token right in the lexer. Using $ for assignments or less static cases than above is prone to surprises since the parser generally operates with one token of lookahead, and $ has to be evaluated before a token even exists. In contrast, # is read in the lexer and evaluated in the parser. So basically the Guile relevant angle of the two is identical. > /ok }#) > Before, you could turn the #{}# into a lambda No, it was turned into a reader expansion. No closure. The reader expansion had as constant contents the #{ ... #} contents as a string, and a capture of the current lexical environment. When this got executed, the parser is let loose on the #{ ... #} contents, and when encountering $ or #, it evaluates them like it would do outside of #{ ... #} except for using a local-eval in the captured environment. In either case, the Scheme reader is used for skipping over the sexp following $ and #. That is still done, but instead of throwing the read sexp away, we now put it in a lambda together with its position in the string (unless it is an obvious constant). When evaluating # or $ at runtime, we first check the alist of positions to lambdas, and if we find a record of that position, evaluate a call to the recorded lambda instead of the read expression. Of course, this is not good for producing lvalues (pardon the Cism). > and get at the $vars and evaluate the #(expressions) in the > procedure-environment of the lambda. Now, you have to munge around in > the expression and, in this case, produce 4 closures: (lambda () bar), > 3 times, and (lambda () (scheme-expression!)). > > Is that right? Yes, that's what we currently do. I don't know whether Scheme will be smart enough to use the same memoization for all lambdas in (list (cons 1 (lambda () bar)) (cons 5 (lambda () bar)) (cons 13 (lambda () bar))) And it is a nuisance that one can't cons that thing together in the reader and just quote it at runtime, because the read extension works in the wrong lexical environment to produce the right kind of lambda. One could at best use two lists instead of an alist, and at least code the position list as a single constant. -- David Kastrup ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-13 16:27 ` Andy Wingo 2011-12-13 16:54 ` David Kastrup @ 2011-12-13 17:28 ` Mark H Weaver 2011-12-13 18:49 ` Andy Wingo 1 sibling, 1 reply; 82+ messages in thread From: Mark H Weaver @ 2011-12-13 17:28 UTC (permalink / raw) To: Andy Wingo; +Cc: David Kastrup, guile-devel Hi Andy, Andy Wingo <wingo@pobox.com> writes: > Am I missing something? It has been a long thread :) In case you haven't carefully read my earlier thread with David, I wanted to briefly explain the difficulties as I understand them, from a Schemer's perspective. If I have misunderstood something, hopefully David will correct me. Most importantly, the design of the Lilypond language fundamentally requires that parsing and execution are done simultaneously. It is not even possible to detect the boundaries between two statements without runtime information. To repeat an example that David provided earlier, consider a Lilypond function that accepts one optional integer argument followed by a required music argument. When the parser/evaluator sees a call to this function, it must determine the dynamic type of the first argument in order to know where the function call ends and where the following code begins. The prime difficulty this causes for us is that when Scheme code contains Lilypond code which contains Scheme code, e.g.: >> (let ((xxx 2)) >> #{ #(set! xxx (1+ xxx)) #}) In the general case, Lilypond needs to _execute_ the outer Scheme code before the parser/evaluator is able to even _see_ the inner Scheme code, because it needs to parse/evaluate the Lily code in between the two, and we've already established that parsing cannot be not be done without runtime information. Therefore, the problem goes beyond simply Scheme within Scheme where the inner Scheme is time-shifted. That much could easily be handled by closures like this: (let ((xxx 2)) (lambda () (set! xxx (1+ xxx)))) The problem is that we need to execute the (let ((xxx 2)) ...) part before we have any idea what code is present in the "...". Furthermore, it is not enough to simply provide an alist of lexical variables at the point of the "...". We need to provide a complete environment sufficient to execute arbitrary Scheme code at that point, which could include things like (set! xxx ...) or (set! (procedure-with-setter xxx ...) ...) or whatever. My suggestion is to provide something like a (capture-lexical-environment) special form that would be put in place of the "...", along with a "local-eval" that makes use of it. Any top-level form containing (capture-lexical-environment) would be interpreted, not compiled. Then, Lilypond could evaluate: (let ((xxx 2)) (capture-lexical-environment)) which would return a lexical environment object, then proceed to parse/evaluate the intervening Lilypond code, and then if needed could call "local-eval" to evaluate any Scheme code within. In the general case this inner Scheme code could also contain a (capture-lexical-environment), and so on. How difficult would it be to implement this? Best, Mark ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-13 17:28 ` Mark H Weaver @ 2011-12-13 18:49 ` Andy Wingo 2011-12-13 19:15 ` Mark H Weaver 0 siblings, 1 reply; 82+ messages in thread From: Andy Wingo @ 2011-12-13 18:49 UTC (permalink / raw) To: Mark H Weaver; +Cc: David Kastrup, guile-devel On Tue 13 Dec 2011 18:28, Mark H Weaver <mhw@netris.org> writes: > >> (let ((xxx 2)) > >> #{ #(set! xxx (1+ xxx)) #}) > In the general case, Lilypond needs to _execute_ the outer Scheme code > before the parser/evaluator is able to even _see_ the inner Scheme code, > because it needs to parse/evaluate the Lily code in between the two, and > we've already established that parsing cannot be not be done without > runtime information. What does it mean to execute a `(let ((xxx 2))' ? I think I need to read the thread again, because I am really not getting it. > (let ((xxx 2)) > (capture-lexical-environment)) > > How difficult would it be to implement this? Dunno. I was thinking that we could have a special form to return a list of the identifiers in scope. Or perhaps, some syntax-bindings procedure, to operate on syntax objects. A macro could use that data to make a closure that captures all of the bindings, if that's your thing. Andy -- http://wingolog.org/ ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-13 18:49 ` Andy Wingo @ 2011-12-13 19:15 ` Mark H Weaver 2011-12-13 23:00 ` Noah Lavine 0 siblings, 1 reply; 82+ messages in thread From: Mark H Weaver @ 2011-12-13 19:15 UTC (permalink / raw) To: Andy Wingo; +Cc: David Kastrup, guile-devel Andy Wingo <wingo@pobox.com> writes: > On Tue 13 Dec 2011 18:28, Mark H Weaver <mhw@netris.org> writes: > >> >> (let ((xxx 2)) >> >> #{ #(set! xxx (1+ xxx)) #}) > >> In the general case, Lilypond needs to _execute_ the outer Scheme code >> before the parser/evaluator is able to even _see_ the inner Scheme code, >> because it needs to parse/evaluate the Lily code in between the two, and >> we've already established that parsing cannot be not be done without >> runtime information. > > What does it mean to execute a `(let ((xxx 2))' ? I think I need to > read the thread again, because I am really not getting it. Well, this example is a bit too simple to illustrate the point. Let's consider a slightly more complex example: (let ((xxx (foobar 1 2))) #{ #(begin (set! xxx (1+ xxx)) (let ((yyy (foobar 3 4))) #{ #(set! yyy (+ xxx yyy)) #} )) #} ) In this case, Lilypond would need to start by evaluating: (let ((xxx (foobar 1 2))) (capture-lexical-environment)) which entails evaluating (foobar 1 2), extending the lexical environment with a binding for "xxx", and then returning a new lexical environment object. Then Lilypond would then continue to parse/evaluate the Lilypond code beginning with #{, which in the general case must be done at the same time as execution. When it finds the #( it enters Scheme mode again, so it would then pass the lexical environment object from the previous step to "local-eval" with the following expression: (begin (set! xxx (1+ xxx)) (let ((yyy (foobar 3 4))) (capture-lexical-environment))) which entails mutating "xxx", evaluating (foobar 3 4) and extending the lexical environment again (which should now contain both xxx and yyy), and then returning a new lexical environment object. And so on. Does this make sense? >> How difficult would it be to implement this? > > Dunno. I was thinking that we could have a special form to return a > list of the identifiers in scope. I don't think this is sufficient. The special form must return a lexical environment object that contains everything needed by a "local-eval" procedure (which we should also provide) to evaluate arbitrary scheme code within that lexical environment. The key is that we must create the lexical environment object before we know anything about the code that will later be passed to "local-eval". Thanks, Mark ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-13 19:15 ` Mark H Weaver @ 2011-12-13 23:00 ` Noah Lavine 2011-12-13 23:16 ` David Kastrup ` (2 more replies) 0 siblings, 3 replies; 82+ messages in thread From: Noah Lavine @ 2011-12-13 23:00 UTC (permalink / raw) To: Mark H Weaver; +Cc: Andy Wingo, David Kastrup, guile-devel Hello, I haven't really been contributing to this thread, so please take my opinion with a grain of salt. But it does appear to me that we should support capturing a lexical environment, as Mark and David describe. So I took a look at ice-9/eval.scm to see how difficult it would be to implement. Offhand, it doesn't look bad: the eval function there already passes around environment objects, so if it hit this special form, it would simply return its environment object (probably packaged up in a record so it would print nicely). Restarting it is also simple: call eval on an expression with the given environment. The environment objects already contain all of the information needed to evaluate expressions, so I don't think there is very much to do there. The part that seems more interesting to me is that Guile's evaluator attempts to memoize an entire expression before evaluating any of it, which I understand is impossible with Lilypond. I assume Lilypond handles this by bundling the Lilypond code into a string (or some other object), letting the memoizer look at that, and then later doing the actual expansion. David, is this how you handle that? Noah On Tue, Dec 13, 2011 at 2:15 PM, Mark H Weaver <mhw@netris.org> wrote: > Andy Wingo <wingo@pobox.com> writes: >> On Tue 13 Dec 2011 18:28, Mark H Weaver <mhw@netris.org> writes: >> >>> >> (let ((xxx 2)) >>> >> #{ #(set! xxx (1+ xxx)) #}) >> >>> In the general case, Lilypond needs to _execute_ the outer Scheme code >>> before the parser/evaluator is able to even _see_ the inner Scheme code, >>> because it needs to parse/evaluate the Lily code in between the two, and >>> we've already established that parsing cannot be not be done without >>> runtime information. >> >> What does it mean to execute a `(let ((xxx 2))' ? I think I need to >> read the thread again, because I am really not getting it. > > Well, this example is a bit too simple to illustrate the point. > Let's consider a slightly more complex example: > > (let ((xxx (foobar 1 2))) > #{ #(begin (set! xxx (1+ xxx)) > (let ((yyy (foobar 3 4))) > #{ #(set! yyy (+ xxx yyy)) #} )) #} ) > > In this case, Lilypond would need to start by evaluating: > > (let ((xxx (foobar 1 2))) > (capture-lexical-environment)) > > which entails evaluating (foobar 1 2), extending the lexical environment > with a binding for "xxx", and then returning a new lexical environment > object. > > Then Lilypond would then continue to parse/evaluate the Lilypond code > beginning with #{, which in the general case must be done at the same > time as execution. When it finds the #( it enters Scheme mode again, > so it would then pass the lexical environment object from the previous > step to "local-eval" with the following expression: > > (begin (set! xxx (1+ xxx)) > (let ((yyy (foobar 3 4))) > (capture-lexical-environment))) > > which entails mutating "xxx", evaluating (foobar 3 4) and extending the > lexical environment again (which should now contain both xxx and yyy), > and then returning a new lexical environment object. And so on. > > Does this make sense? > >>> How difficult would it be to implement this? >> >> Dunno. I was thinking that we could have a special form to return a >> list of the identifiers in scope. > > I don't think this is sufficient. The special form must return a > lexical environment object that contains everything needed by a > "local-eval" procedure (which we should also provide) to evaluate > arbitrary scheme code within that lexical environment. > > The key is that we must create the lexical environment object before we > know anything about the code that will later be passed to "local-eval". > > Thanks, > Mark > ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-13 23:00 ` Noah Lavine @ 2011-12-13 23:16 ` David Kastrup 2011-12-13 23:44 ` Andy Wingo 2011-12-13 23:39 ` Andy Wingo 2011-12-14 1:30 ` Mark H Weaver 2 siblings, 1 reply; 82+ messages in thread From: David Kastrup @ 2011-12-13 23:16 UTC (permalink / raw) To: Noah Lavine; +Cc: Andy Wingo, Mark H Weaver, guile-devel Noah Lavine <noah.b.lavine@gmail.com> writes: > Hello, > > I haven't really been contributing to this thread, so please take my > opinion with a grain of salt. But it does appear to me that we should > support capturing a lexical environment, as Mark and David describe. > > So I took a look at ice-9/eval.scm to see how difficult it would be to > implement. Offhand, it doesn't look bad: the eval function there > already passes around environment objects, so if it hit this special > form, it would simply return its environment object (probably packaged > up in a record so it would print nicely). Restarting it is also > simple: call eval on an expression with the given environment. The > environment objects already contain all of the information needed to > evaluate expressions, so I don't think there is very much to do there. > > The part that seems more interesting to me is that Guile's evaluator > attempts to memoize an entire expression before evaluating any of it, > which I understand is impossible with Lilypond. I assume Lilypond > handles this by bundling the Lilypond code into a string (or some > other object), letting the memoizer look at that, and then later doing > the actual expansion. David, is this how you handle that? guile> '#{ \relative c' { $p 2 \mark #4 } #} (#<procedure embedded-lilypond (parser lily-string filename line closures)> parser " \\relative c' { $p 2 \\mark #4 } " #f 2 (list (cons 17 (lambda () p)))) In this case, $p is placed into a lambda together with its offset in the string. The whole thing is a function call to a procedure that clones the current parser and let's it parse the enclosed string. Filename and line are just tracked for providing useful error messages. #4 is not turned into a lambda since it is an obvious constant. The obvious wishlist item would be to replace the potentially long and expensive to create argument "closures" (which needs to get consed together on each call since (lambda () p) needs to be created in the current environment) with (package-environment). I have no reliable idea what "memoize" means exactly in Guile's terminology, and the parts of the manual I have consulted have no qualms using this expression, but don't bother explaining it. -- David Kastrup ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-13 23:16 ` David Kastrup @ 2011-12-13 23:44 ` Andy Wingo 0 siblings, 0 replies; 82+ messages in thread From: Andy Wingo @ 2011-12-13 23:44 UTC (permalink / raw) To: David Kastrup; +Cc: guile-devel, Mark H Weaver On Wed 14 Dec 2011 00:16, David Kastrup <dak@gnu.org> writes: > guile> '#{ \relative c' { $p 2 \mark #4 } #} > (#<procedure embedded-lilypond (parser lily-string filename line closures)> parser " \\relative c' { $p 2 \\mark #4 } " #f 2 (list (cons 17 (lambda () p)))) Ah, thanks for this example. Thanks also for the previous example music mail. > The obvious wishlist item would be to replace the potentially long and > expensive to create argument "closures" (which needs to get consed > together on each call since (lambda () p) needs to be created in the > current environment) with (package-environment). I can see how this is a bit irritating, but at least if you are running with the compiler, this is likely to be significantly faster than 1.8. > I have no reliable idea what "memoize" means exactly in Guile's > terminology, and the parts of the manual I have consulted have no qualms > using this expression, but don't bother explaining it. There are only two instances of this word in the Guile 2.0 manual :) Andy -- http://wingolog.org/ ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-13 23:00 ` Noah Lavine 2011-12-13 23:16 ` David Kastrup @ 2011-12-13 23:39 ` Andy Wingo 2011-12-13 23:45 ` David Kastrup ` (3 more replies) 2011-12-14 1:30 ` Mark H Weaver 2 siblings, 4 replies; 82+ messages in thread From: Andy Wingo @ 2011-12-13 23:39 UTC (permalink / raw) To: Noah Lavine; +Cc: Mark H Weaver, David Kastrup, guile-devel On Wed 14 Dec 2011 00:00, Noah Lavine <noah.b.lavine@gmail.com> writes: > I haven't really been contributing to this thread, so please take my > opinion with a grain of salt. But it does appear to me that we should > support capturing a lexical environment, as Mark and David describe. > > So I took a look at ice-9/eval.scm.... The details of the interpreter's implementation are not public, I'm afraid. The interpreter does its job, but not quickly, and any change to make it better would involve a change to the environment representation. Anyway, it's looking in the wrong place. There is a compiler too. Andy -- http://wingolog.org/ ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-13 23:39 ` Andy Wingo @ 2011-12-13 23:45 ` David Kastrup 2011-12-14 10:15 ` Andy Wingo 2011-12-14 0:30 ` Mark H Weaver ` (2 subsequent siblings) 3 siblings, 1 reply; 82+ messages in thread From: David Kastrup @ 2011-12-13 23:45 UTC (permalink / raw) To: guile-devel Andy Wingo <wingo@pobox.com> writes: > On Wed 14 Dec 2011 00:00, Noah Lavine <noah.b.lavine@gmail.com> writes: > >> I haven't really been contributing to this thread, so please take my >> opinion with a grain of salt. But it does appear to me that we should >> support capturing a lexical environment, as Mark and David describe. >> >> So I took a look at ice-9/eval.scm.... > > The details of the interpreter's implementation are not public, I'm > afraid. The interpreter does its job, but not quickly, and any change > to make it better would involve a change to the environment > representation. > > Anyway, it's looking in the wrong place. There is a compiler too. We might be miscommunicating here. Lilypond calls eval on the # and $ scraps (though I don't know whether that would be ice-9 or not). Actually, I have no idea what else it could call. -- David Kastrup ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-13 23:45 ` David Kastrup @ 2011-12-14 10:15 ` Andy Wingo 2011-12-14 10:32 ` David Kastrup 0 siblings, 1 reply; 82+ messages in thread From: Andy Wingo @ 2011-12-14 10:15 UTC (permalink / raw) To: David Kastrup; +Cc: guile-devel On Wed 14 Dec 2011 00:45, David Kastrup <dak@gnu.org> writes: > Andy Wingo <wingo@pobox.com> writes: > >> On Wed 14 Dec 2011 00:00, Noah Lavine <noah.b.lavine@gmail.com> writes: >> >>> I haven't really been contributing to this thread, so please take my >>> opinion with a grain of salt. But it does appear to me that we should >>> support capturing a lexical environment, as Mark and David describe. >>> >>> So I took a look at ice-9/eval.scm.... >> >> There is a compiler too. > > Lilypond calls eval on the # and $ scraps (though I don't know whether > that would be ice-9 or not). Actually, I have no idea what else it > could call. It could call `compile', in 2.0. It probably doesn't want to though. It sounds a bit academic, but this is not a moot point: as things are now, one can replace any call to `eval' with `compile'. Or, replace the implementation of `eval' with `compile'. I suppose local-eval would be a different beast, though. Regards, Andy -- http://wingolog.org/ ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-14 10:15 ` Andy Wingo @ 2011-12-14 10:32 ` David Kastrup 0 siblings, 0 replies; 82+ messages in thread From: David Kastrup @ 2011-12-14 10:32 UTC (permalink / raw) To: Andy Wingo; +Cc: guile-devel Andy Wingo <wingo@pobox.com> writes: > On Wed 14 Dec 2011 00:45, David Kastrup <dak@gnu.org> writes: > >> Andy Wingo <wingo@pobox.com> writes: >> >>> On Wed 14 Dec 2011 00:00, Noah Lavine <noah.b.lavine@gmail.com> writes: >>> >>>> I haven't really been contributing to this thread, so please take my >>>> opinion with a grain of salt. But it does appear to me that we should >>>> support capturing a lexical environment, as Mark and David describe. >>>> >>>> So I took a look at ice-9/eval.scm.... >>> >>> There is a compiler too. >> >> Lilypond calls eval on the # and $ scraps (though I don't know whether >> that would be ice-9 or not). Actually, I have no idea what else it >> could call. > > It could call `compile', in 2.0. It probably doesn't want to though. > > It sounds a bit academic, but this is not a moot point: as things are > now, one can replace any call to `eval' with `compile'. When would that be an advantage? I could imagine compiling a loop makes sense, but for most things evaluated once, the effort of compiling would not appear to offset the savings. -- David Kastrup ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-13 23:39 ` Andy Wingo 2011-12-13 23:45 ` David Kastrup @ 2011-12-14 0:30 ` Mark H Weaver 2011-12-14 8:16 ` David Kastrup 2011-12-14 0:42 ` Noah Lavine 2011-12-14 0:47 ` Noah Lavine 3 siblings, 1 reply; 82+ messages in thread From: Mark H Weaver @ 2011-12-14 0:30 UTC (permalink / raw) To: Andy Wingo; +Cc: David Kastrup, guile-devel Andy Wingo <wingo@pobox.com> writes: > On Wed 14 Dec 2011 00:00, Noah Lavine <noah.b.lavine@gmail.com> writes: >> I haven't really been contributing to this thread, so please take my >> opinion with a grain of salt. But it does appear to me that we should >> support capturing a lexical environment, as Mark and David describe. >> >> So I took a look at ice-9/eval.scm.... > > The details of the interpreter's implementation are not public, I'm > afraid. The interpreter does its job, but not quickly, and any change > to make it better would involve a change to the environment > representation. I agree that the returned "lexical environment object" should opaque. Probably the only operation that needs this object is "local-eval", though I'm not sure there's any disadvantage to printing it in human-readable form for debugging purposes. It might also be nice to provide a procedure that converts it into an alist of some sort, but that's not strictly needed. I believe this would give us plenty of freedom to change the environment representation in the future, no? > Anyway, it's looking in the wrong place. There is a compiler too. The most obvious implementation of (capture-lexical-environment) would inhibit compilation of any top-level form that contains it. Therefore, the only thing the compiler would need to do is detect the presence of (capture-lexical-environment), and in that case, abort compilation of the entire top-level form. I guess such a form should be "compiled" simply as a call to the evaluator with the entire top-level form as its argument. This would all happen after macro expansion, of course. Does this make sense? Mark ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-14 0:30 ` Mark H Weaver @ 2011-12-14 8:16 ` David Kastrup 0 siblings, 0 replies; 82+ messages in thread From: David Kastrup @ 2011-12-14 8:16 UTC (permalink / raw) To: Mark H Weaver; +Cc: Andy Wingo, guile-devel Mark H Weaver <mhw@netris.org> writes: > Andy Wingo <wingo@pobox.com> writes: >> On Wed 14 Dec 2011 00:00, Noah Lavine <noah.b.lavine@gmail.com> writes: >>> I haven't really been contributing to this thread, so please take my >>> opinion with a grain of salt. But it does appear to me that we should >>> support capturing a lexical environment, as Mark and David describe. >>> >>> So I took a look at ice-9/eval.scm.... >> >> The details of the interpreter's implementation are not public, I'm >> afraid. The interpreter does its job, but not quickly, and any change >> to make it better would involve a change to the environment >> representation. > > I agree that the returned "lexical environment object" should opaque. > Probably the only operation that needs this object is "local-eval", > though I'm not sure there's any disadvantage to printing it in > human-readable form for debugging purposes. I was actually somewhat surprised that one could not just use "eval" here and give it the environment instead of a module argument. It is not actually obvious from the documentation whether the active module is part of the environment or not. To be fair, not much is obvious from the documentation of local-eval (documented in Guile 1.8, one occurence of "memoized" in this version). @c snarfed from debug.c:406 @deffn {Scheme Procedure} local-eval exp [env] @deffnx {C Function} scm_local_eval (exp, env) Evaluate @var{exp} in its environment. If @var{env} is supplied, it is the environment in which to evaluate @var{exp}. Otherwise, @var{exp} must be a memoized code object (in which case, its environment is implicit). @end deffn >> Anyway, it's looking in the wrong place. There is a compiler too. > > The most obvious implementation of (capture-lexical-environment) would > inhibit compilation of any top-level form that contains it. Well, it probably is less invasive than the equivalent of marking every local variable (similarly to global ones) as subject to change across calls. > Therefore, the only thing the compiler would need to do is detect the > presence of (capture-lexical-environment), and in that case, abort > compilation of the entire top-level form. I guess such a form should > be "compiled" simply as a call to the evaluator with the entire > top-level form as its argument. This would all happen after macro > expansion, of course. > > Does this make sense? Sounds a bit heavy-handed. For the use cases in Lilypond, I don't expect noticeable slow-downs as long as "the evaluator" works with reasonable performance for an interpreter and is not coded with the "we have a compiler anyway, so this will only run in exceptional cases and can be exceptionally slow" frame of mind. But it does a reasonably simple job, and probably is not significantly changed from Guile v1, so I don't expect much of a problem here. So this would basically work for Lilypond. It would, however, be likely a good idea to have it in a state where attaching strong-worded warnings to the documentation is not necessary. Something like "If an environment is being captured, the enclosing code can't be optimized (in the current implementation, it is simply left uncompiled). So it is a good idea to place performance-critical code in functions separate from those where you need to capture the environment." should likely be enough. -- David Kastrup ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-13 23:39 ` Andy Wingo 2011-12-13 23:45 ` David Kastrup 2011-12-14 0:30 ` Mark H Weaver @ 2011-12-14 0:42 ` Noah Lavine 2011-12-14 0:47 ` Noah Lavine 3 siblings, 0 replies; 82+ messages in thread From: Noah Lavine @ 2011-12-14 0:42 UTC (permalink / raw) To: Andy Wingo; +Cc: Mark H Weaver, David Kastrup, guile-devel On Tue, Dec 13, 2011 at 6:39 PM, Andy Wingo <wingo@pobox.com> wrote: > On Wed 14 Dec 2011 00:00, Noah Lavine <noah.b.lavine@gmail.com> writes: > >> I haven't really been contributing to this thread, so please take my >> opinion with a grain of salt. But it does appear to me that we should >> support capturing a lexical environment, as Mark and David describe. >> >> So I took a look at ice-9/eval.scm.... > > The details of the interpreter's implementation are not public, I'm > afraid. The interpreter does its job, but not quickly, and any change > to make it better would involve a change to the environment > representation. Oh, I just realized that I miscommunicated very badly. When I said "print nicely", I imagined something like "<procedure environement object>". So we could hide the implementation inside the record type. Noah ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-13 23:39 ` Andy Wingo ` (2 preceding siblings ...) 2011-12-14 0:42 ` Noah Lavine @ 2011-12-14 0:47 ` Noah Lavine 3 siblings, 0 replies; 82+ messages in thread From: Noah Lavine @ 2011-12-14 0:47 UTC (permalink / raw) To: Andy Wingo; +Cc: Mark H Weaver, David Kastrup, guile-devel > The details of the interpreter's implementation are not public, I'm > afraid. The interpreter does its job, but not quickly, and any change > to make it better would involve a change to the environment > representation. > > Anyway, it's looking in the wrong place. There is a compiler too. And since it seems to be my day to send poorly-thought-out emails, here's another issue. Yes, evaluation should almost always be done by the compiler. I was imagining that procedures that use the (capture-local-environment) form would be interpreted, because using the compiler there could get ugly. (I realize it's possible to use the compiler there somewhat, but it seems easier at first to just not do it.) That's why I was looking at primitive-eval. Thinking about it now, I can imagine a sort of hybrid compiler-interpreter implementation strategy, but I'm not sure if that's the right thing to do here. It's a lot more complexity. Have a nice day, Noah ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-13 23:00 ` Noah Lavine 2011-12-13 23:16 ` David Kastrup 2011-12-13 23:39 ` Andy Wingo @ 2011-12-14 1:30 ` Mark H Weaver 2011-12-14 7:50 ` Mark H Weaver 2 siblings, 1 reply; 82+ messages in thread From: Mark H Weaver @ 2011-12-14 1:30 UTC (permalink / raw) To: Noah Lavine; +Cc: Andy Wingo, David Kastrup, guile-devel Hi Noah, Noah Lavine <noah.b.lavine@gmail.com> writes: > So I took a look at ice-9/eval.scm to see how difficult it would be to > implement. Offhand, it doesn't look bad: the eval function there > already passes around environment objects, so if it hit this special > form, it would simply return its environment object (probably packaged > up in a record so it would print nicely). Restarting it is also > simple: call eval on an expression with the given environment. The > environment objects already contain all of the information needed to > evaluate expressions, so I don't think there is very much to do there. > > The part that seems more interesting to me is that Guile's evaluator > attempts to memoize an entire expression before evaluating any of it, > which I understand is impossible with Lilypond. I also looked at eval.scm, and I see a complication. The environments passed around in ice-9/eval.scm do not contain any variable names. The evaluator works with memoized expressions, where variable references are replaced by indices into the environment. Therefore, the memoizer would need to be enhanced to support (capture-lexical-environment) specially. The evaluator may not know the variable names, but the memoizer does. After all, one of its jobs is to convert variable references from symbols to indices. The most straightforward solution I see is the following: When the memoizer converts (capture-lexical-environment) into a memoized object, it should embed within that object its own "memoizer environment" (a list of variable names). Then, when the (capture-lexical-environment) is evaluated, it simply bundles together both the memoizer's environment (a list of variable names) and the evaluator's environment (a list of variable values) into an opaque lexical environment object. When "local-eval" is called, it will need to first call the memoizer and then the evaluator. It will have everything it needs to do this within the lexical environment object. What do you think? Best, Mark ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-14 1:30 ` Mark H Weaver @ 2011-12-14 7:50 ` Mark H Weaver 2011-12-14 8:48 ` [PATCH] Implement `capture-lexical-environment' in evaluator Mark H Weaver 2011-12-14 10:08 ` Anything better for delayed lexical evaluation than (lambda () ...)? Andy Wingo 0 siblings, 2 replies; 82+ messages in thread From: Mark H Weaver @ 2011-12-14 7:50 UTC (permalink / raw) To: Noah Lavine; +Cc: Andy Wingo, David Kastrup, guile-devel I have successfully implemented the (capture-lexical-environment) special form in the evaluator, and also primitive-local-eval. Here's a transcript from a test session: scheme@(guile-user)> (define env1 (primitive-eval '(let ((x 1) (y 2)) (capture-lexical-environment)))) scheme@(guile-user)> (primitive-local-eval 'x env1) $2 = 1 scheme@(guile-user)> (primitive-local-eval 'y env1) $3 = 2 scheme@(guile-user)> (primitive-local-eval '(set! x (+ x 10)) env1) $4 = 11 scheme@(guile-user)> (primitive-local-eval 'x env1) $5 = 11 scheme@(guile-user)> (define env2 (primitive-local-eval '(begin (set! x (+ x 1)) (let ((z 3)) (capture-lexical-environment))) env1)) scheme@(guile-user)> (primitive-local-eval 'z env2) $7 = 3 scheme@(guile-user)> (primitive-local-eval 'x env2) $8 = 12 scheme@(guile-user)> (primitive-local-eval 'y env2) $9 = 2 Note that in addition to the evaluator and memoizer environments, I also needed to add an expander environment to the overall lexical environment object. I will post a preliminary patch shortly. It's fairly small. Mark ^ permalink raw reply [flat|nested] 82+ messages in thread
* [PATCH] Implement `capture-lexical-environment' in evaluator 2011-12-14 7:50 ` Mark H Weaver @ 2011-12-14 8:48 ` Mark H Weaver 2011-12-14 9:08 ` David Kastrup ` (2 more replies) 2011-12-14 10:08 ` Anything better for delayed lexical evaluation than (lambda () ...)? Andy Wingo 1 sibling, 3 replies; 82+ messages in thread From: Mark H Weaver @ 2011-12-14 8:48 UTC (permalink / raw) To: Noah Lavine; +Cc: Andy Wingo, David Kastrup, guile-devel [-- Attachment #1: Type: text/plain, Size: 2557 bytes --] This is a _preliminary_ patch. In particular: * The compiler does not yet handle (capture-lexical-environment) (which uses a new tree-il type). * The lexical environment object is currently non-opaque list structure. * I deliberately avoided reindenting eval.scm so that the non-whitespace changes would be evident, to make review easier. * I wouldn't be surprised if `primitive-local-eval' does the wrong thing if (current-module) is different from what it was when the associated `primitive-eval' was called. * I manually removed the psyntax-pp.scm patch from the output of git-format-patch (though the header change summary still mentions it), since it was so huge. I guess you'll need to manually regenerate that file yourself, since the Makefiles don't do it automatically: cd guile/module; make ice-9/psyntax-pp.scm.gen Here's an example session: mhw:~/guile$ meta/guile GNU Guile 2.0.3.66-52b7f-dirty Copyright (C) 1995-2011 Free Software Foundation, Inc. Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'. This program is free software, and you are welcome to redistribute it under certain conditions; type `,show c' for details. Enter `,help' for help. scheme@(guile-user)> (define env1 (primitive-eval '(let ((x 1) (y 2)) (capture-lexical-environment)))) scheme@(guile-user)> (primitive-local-eval 'x env1) $1 = 1 scheme@(guile-user)> (primitive-local-eval 'y env1) $2 = 2 scheme@(guile-user)> (primitive-local-eval '(set! x (+ x 10)) env1) $3 = 11 scheme@(guile-user)> (primitive-local-eval 'x env1) $4 = 11 scheme@(guile-user)> (define env2 (primitive-local-eval '(begin (set! x (+ x 1)) (let ((z 3)) (capture-lexical-environment))) env1)) scheme@(guile-user)> (primitive-local-eval 'z env2) $5 = 3 scheme@(guile-user)> (primitive-local-eval 'x env2) $6 = 12 scheme@(guile-user)> (primitive-local-eval 'y env2) $7 = 2 scheme@(guile-user)> (primitive-local-eval 'x env1) $8 = 12 scheme@(guile-user)> (primitive-local-eval '(set! x (+ x 10)) env1) $9 = 22 scheme@(guile-user)> (primitive-local-eval 'x env2) $10 = 22 scheme@(guile-user)> (primitive-local-eval '(set! y (+ y 5)) env2) $11 = 7 scheme@(guile-user)> (primitive-local-eval 'y env1) $12 = 7 scheme@(guile-user)> (define foo 35) scheme@(guile-user)> (primitive-local-eval 'foo env1) $13 = 35 scheme@(guile-user)> (primitive-local-eval '(set! foo 37) env1) scheme@(guile-user)> foo $14 = 37 The preliminary patch follows. Comments solicited. Mark [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: Implement `capture-lexical-environment' in evaluator --] [-- Type: text/x-patch, Size: 12928 bytes --] From 417762cbd3d299bb166ac240bc84fcceeb6dcde9 Mon Sep 17 00:00:00 2001 From: Mark H Weaver <mhw@netris.org> Date: Wed, 14 Dec 2011 03:12:43 -0500 Subject: [PATCH] Implement `capture-lexical-environment' in evaluator PRELIMINARY WORK, not ready for commit. --- libguile/expand.c | 5 + libguile/expand.h | 13 + libguile/memoize.c | 18 + libguile/memoize.h | 5 +- module/ice-9/eval.scm | 49 +- module/ice-9/psyntax-pp.scm |23314 ++++++++++++++++++++++--------------------- module/ice-9/psyntax.scm | 32 +- module/language/tree-il.scm | 10 + 8 files changed, 12127 insertions(+), 11319 deletions(-) diff --git a/module/ice-9/eval.scm b/module/ice-9/eval.scm index c0fa64c..e51c662 100644 --- a/module/ice-9/eval.scm +++ b/module/ice-9/eval.scm @@ -213,7 +213,16 @@ ;;; `eval' in this order, to put the most frequent cases first. ;;; -(define primitive-eval +;; FIXME: make this opaque!! +(define (make-lexical-environment eval-env memo-env expander-env) + (list '<lexical-environment> eval-env memo-env expander-env)) +(define lexical-environment:eval-env cadr) +(define lexical-environment:memo-env caddr) +(define lexical-environment:expander-env cadddr) + +(define primitive-eval #f) +(define primitive-local-eval #f) + (let () ;; We pre-generate procedures with fixed arities, up to some number of ;; arguments; see make-fixed-closure above. @@ -459,6 +468,9 @@ (eval exp env) (eval handler env))) + (('capture-lexical-environment (memo-env . expander-env)) + (make-lexical-environment env memo-env expander-env)) + (('call/cc proc) (call/cc (eval proc env))) @@ -469,12 +481,29 @@ (memoize-variable-access! exp #f)) (eval x env))))) - ;; primitive-eval - (lambda (exp) - "Evaluate @var{exp} in the current module." - (eval - (memoize-expression - (if (macroexpanded? exp) - exp - ((module-transformer (current-module)) exp))) - '())))) + (set! primitive-local-eval + (lambda (exp env) + "Evaluate @var{exp} within the lexical environment @var{env}." + (let ((eval-env (lexical-environment:eval-env env)) + (memo-env (lexical-environment:memo-env env)) + (expander-env (lexical-environment:expander-env env))) + (let ((module (capture-env (if (pair? eval-env) + (cdr (last-pair eval-env)) + eval-env)))) + (eval + (memoize-local-expression + (if (macroexpanded? exp) + exp + ((module-transformer module) exp #:env expander-env)) + memo-env) + eval-env))))) + + (set! primitive-eval + (lambda (exp) + "Evaluate @var{exp} in the current module." + (eval + (memoize-expression + (if (macroexpanded? exp) + exp + ((module-transformer (current-module)) exp))) + '())))) diff --git a/libguile/memoize.c b/libguile/memoize.c index 911d972..c06d593 100644 --- a/libguile/memoize.c +++ b/libguile/memoize.c @@ -112,6 +112,8 @@ scm_t_bits scm_tc16_memoized; MAKMEMO (SCM_M_MODULE_SET, scm_cons (val, scm_cons (mod, scm_cons (var, public)))) #define MAKMEMO_PROMPT(tag, exp, handler) \ MAKMEMO (SCM_M_PROMPT, scm_cons (tag, scm_cons (exp, handler))) +#define MAKMEMO_CAPTURE_LEXICAL_ENVIRONMENT(memo_env, expander_env) \ + MAKMEMO (SCM_M_CAPTURE_LEXICAL_ENVIRONMENT, scm_cons(memo_env, expander_env)) /* Primitives for the evaluator */ @@ -143,6 +145,7 @@ static const char *const memoized_tags[] = "module-ref", "module-set!", "prompt", + "capture-lexical-environment", }; static int @@ -426,6 +429,9 @@ memoize (SCM exp, SCM env) memoize_exps (REF (exp, DYNLET, VALS), env), memoize (REF (exp, DYNLET, BODY), env)); + case SCM_EXPANDED_CAPTURE_LEXICAL_ENVIRONMENT: + return MAKMEMO_CAPTURE_LEXICAL_ENVIRONMENT (env, REF (exp, CAPTURE_LEXICAL_ENVIRONMENT, ENV)); + default: abort (); } @@ -444,6 +450,16 @@ SCM_DEFINE (scm_memoize_expression, "memoize-expression", 1, 0, 0, } #undef FUNC_NAME +SCM_DEFINE (scm_memoize_local_expression, "memoize-local-expression", 2, 0, 0, + (SCM exp, SCM env), + "Memoize the expression @var{exp} within the local memoize environment @var{env}.") +#define FUNC_NAME s_scm_memoize_local_expression +{ + SCM_ASSERT_TYPE (SCM_EXPANDED_P (exp), exp, 1, FUNC_NAME, "expanded"); + return memoize (exp, env); +} +#undef FUNC_NAME + \f @@ -706,6 +722,8 @@ unmemoize (const SCM expr) unmemoize (CAR (args)), unmemoize (CADR (args)), unmemoize (CDDR (args))); + case SCM_M_CAPTURE_LEXICAL_ENVIRONMENT: + return scm_list_3 (scm_sym_capture_lexical_environment, CAR (args), CDR (args)); default: abort (); } diff --git a/libguile/memoize.h b/libguile/memoize.h index 26bd5b1..4a05bee 100644 --- a/libguile/memoize.h +++ b/libguile/memoize.h @@ -44,6 +44,7 @@ SCM_API SCM scm_sym_quote; SCM_API SCM scm_sym_quasiquote; SCM_API SCM scm_sym_unquote; SCM_API SCM scm_sym_uq_splicing; +SCM_API SCM scm_sym_capture_lexical_environment; SCM_API SCM scm_sym_with_fluids; SCM_API SCM scm_sym_at; @@ -90,13 +91,15 @@ enum SCM_M_TOPLEVEL_SET, SCM_M_MODULE_REF, SCM_M_MODULE_SET, - SCM_M_PROMPT + SCM_M_PROMPT, + SCM_M_CAPTURE_LEXICAL_ENVIRONMENT }; \f SCM_INTERNAL SCM scm_memoize_expression (SCM exp); +SCM_INTERNAL SCM scm_memoize_local_expression (SCM exp, SCM env); SCM_INTERNAL SCM scm_unmemoize_expression (SCM memoized); SCM_INTERNAL SCM scm_memoized_expression_typecode (SCM memoized); SCM_INTERNAL SCM scm_memoized_expression_data (SCM memoized); diff --git a/libguile/expand.h b/libguile/expand.h index 02e6e17..b78ef1b 100644 --- a/libguile/expand.h +++ b/libguile/expand.h @@ -54,6 +54,7 @@ typedef enum SCM_EXPANDED_LET, SCM_EXPANDED_LETREC, SCM_EXPANDED_DYNLET, + SCM_EXPANDED_CAPTURE_LEXICAL_ENVIRONMENT, SCM_NUM_EXPANDED_TYPES, } scm_t_expanded_type; @@ -330,6 +331,18 @@ enum #define SCM_MAKE_EXPANDED_DYNLET(src, fluids, vals, body) \ scm_c_make_struct (exp_vtables[SCM_EXPANDED_DYNLET], 0, SCM_NUM_EXPANDED_DYNLET_FIELDS, SCM_UNPACK (src), SCM_UNPACK (fluids), SCM_UNPACK (vals), SCM_UNPACK (body)) +#define SCM_EXPANDED_CAPTURE_LEXICAL_ENVIRONMENT_TYPE_NAME "capture-lexical-environment" +#define SCM_EXPANDED_CAPTURE_LEXICAL_ENVIRONMENT_FIELD_NAMES \ + { "src", "env", } +enum + { + SCM_EXPANDED_CAPTURE_LEXICAL_ENVIRONMENT_SRC, + SCM_EXPANDED_CAPTURE_LEXICAL_ENVIRONMENT_ENV, + SCM_NUM_EXPANDED_CAPTURE_LEXICAL_ENVIRONMENT_FIELDS, + }; +#define SCM_MAKE_EXPANDED_CAPTURE_LEXICAL_ENVIRONMENT(src, env) \ + scm_c_make_struct (exp_vtables[SCM_EXPANDED_CAPTURE_LEXICAL_ENVIRONMENT], 0, SCM_NUM_EXPANDED_CAPTURE_LEXICAL_ENVIRONMENT_FIELDS, SCM_UNPACK (src), SCM_UNPACK (env)) + #endif /* BUILDING_LIBGUILE */ \f diff --git a/libguile/expand.c b/libguile/expand.c index bdecd80..35d1c3a 100644 --- a/libguile/expand.c +++ b/libguile/expand.c @@ -85,6 +85,8 @@ static const char** exp_field_names[SCM_NUM_EXPANDED_TYPES]; SCM_MAKE_EXPANDED_LETREC(src, in_order_p, names, gensyms, vals, body) #define DYNLET(src, fluids, vals, body) \ SCM_MAKE_EXPANDED_DYNLET(src, fluids, vals, body) +#define CAPTURE_LEXICAL_ENVIRONMENT(src, env) \ + SCM_MAKE_EXPANDED_CAPTURE_LEXICAL_ENVIRONMENT(src, env) #define CAR(x) SCM_CAR(x) #define CDR(x) SCM_CDR(x) @@ -203,6 +205,8 @@ SCM_GLOBAL_SYMBOL (scm_sym_unquote, "unquote"); SCM_GLOBAL_SYMBOL (scm_sym_quasiquote, "quasiquote"); SCM_GLOBAL_SYMBOL (scm_sym_uq_splicing, "unquote-splicing"); +SCM_GLOBAL_SYMBOL (scm_sym_capture_lexical_environment, "capture-lexical-environment"); + SCM_KEYWORD (kw_allow_other_keys, "allow-other-keys"); SCM_KEYWORD (kw_optional, "optional"); SCM_KEYWORD (kw_key, "key"); @@ -1250,6 +1254,7 @@ scm_init_expand () DEFINE_NAMES (LET); DEFINE_NAMES (LETREC); DEFINE_NAMES (DYNLET); + DEFINE_NAMES (CAPTURE_LEXICAL_ENVIRONMENT); scm_exp_vtable_vtable = scm_make_vtable (scm_from_locale_string (SCM_VTABLE_BASE_LAYOUT "pwuwpw"), diff --git a/module/language/tree-il.scm b/module/language/tree-il.scm index 1d391c4..455dccc 100644 --- a/module/language/tree-il.scm +++ b/module/language/tree-il.scm @@ -49,6 +49,9 @@ <dynlet> dynlet? make-dynlet dynlet-src dynlet-fluids dynlet-vals dynlet-body <dynref> dynref? make-dynref dynref-src dynref-fluid <dynset> dynset? make-dynset dynset-src dynset-fluid dynset-exp + <capture-lexical-environment> capture-lexical-environment? + make-capture-lexical-environment + capture-lexical-environment-src <prompt> prompt? make-prompt prompt-src prompt-tag prompt-body prompt-handler <abort> abort? make-abort abort-src abort-tag abort-args abort-tail @@ -125,6 +128,7 @@ ;; (<let> names gensyms vals body) ;; (<letrec> in-order? names gensyms vals body) ;; (<dynlet> fluids vals body) + ;; (<capture-lexical-environment>) (define-type (<tree-il> #:common-slots (src) #:printer print-tree-il) (<fix> names gensyms vals body) @@ -324,6 +328,9 @@ ((<dynset> fluid exp) `(dynset ,(unparse-tree-il fluid) ,(unparse-tree-il exp))) + ((<capture-lexical-environment>) + '(capture-lexical-environment)) + ((<prompt> tag body handler) `(prompt ,(unparse-tree-il tag) ,(unparse-tree-il body) ,(unparse-tree-il handler))) @@ -470,6 +477,9 @@ ((<dynset> fluid exp) `(fluid-set! ,(tree-il->scheme fluid) ,(tree-il->scheme exp))) + ((<capture-lexical-environment>) + '(capture-lexical-environment)) + ((<prompt> tag body handler) `(call-with-prompt ,(tree-il->scheme tag) diff --git a/module/ice-9/psyntax.scm b/module/ice-9/psyntax.scm index e522f54..d147902 100644 --- a/module/ice-9/psyntax.scm +++ b/module/ice-9/psyntax.scm @@ -307,6 +307,14 @@ (if (not (assq 'name meta)) (set-lambda-meta! val (acons 'name name meta)))))) + ;; data type for exporting the compile-type environment + ;; FIXME: make this opaque! + (define (make-psyntax-env r w mod) + (list '<psyntax-env> r w mod)) + (define psyntax-env:r cadr) + (define psyntax-env:w caddr) + (define psyntax-env:mod cadddr) + ;; output constructors (define build-void (lambda (source) @@ -410,6 +418,9 @@ (define (build-data src exp) (make-const src exp)) + (define (build-capture-lexical-environment src env) + (make-capture-lexical-environment src env)) + (define build-sequence (lambda (src exps) (if (null? (cdr exps)) @@ -1786,6 +1797,13 @@ (_ (syntax-violation 'quote "bad syntax" (source-wrap e w s mod)))))) + (global-extend 'core 'capture-lexical-environment + (lambda (e r w s mod) + (syntax-case e () + ((_) (build-capture-lexical-environment s (make-psyntax-env r w mod))) + (_ (syntax-violation 'quote "bad syntax" + (source-wrap e w s mod)))))) + (global-extend 'core 'syntax (let () (define gen-syntax @@ -2395,10 +2413,17 @@ ;; expanded, and the expanded definitions are also residualized into ;; the object file if we are compiling a file. (set! macroexpand - (lambda* (x #:optional (m 'e) (esew '(eval))) - (expand-top-sequence (list x) null-env top-wrap #f m esew - (cons 'hygiene (module-name (current-module)))))) + (lambda* (x #:optional (m 'e) (esew '(eval)) + #:key (env (make-psyntax-env + null-env top-wrap + (cons 'hygiene (module-name (current-module)))))) + (expand-top-sequence (list x) + (psyntax-env:r env) ;; null-env + (psyntax-env:w env) ;; top-wrap + #f + m + esew + (psyntax-env:mod env)))) ;; (cons 'hygiene (module-name (current-module))) (set! identifier? (lambda (x) -- 1.7.5.4 ^ permalink raw reply related [flat|nested] 82+ messages in thread
* Re: [PATCH] Implement `capture-lexical-environment' in evaluator 2011-12-14 8:48 ` [PATCH] Implement `capture-lexical-environment' in evaluator Mark H Weaver @ 2011-12-14 9:08 ` David Kastrup 2011-12-14 9:36 ` Mark H Weaver 2011-12-16 9:21 ` [PATCH] Implement `the-environment' and `local-eval' " Mark H Weaver 2 siblings, 0 replies; 82+ messages in thread From: David Kastrup @ 2011-12-14 9:08 UTC (permalink / raw) To: guile-devel Mark H Weaver <mhw@netris.org> writes: > This is a _preliminary_ patch. In particular: > > * The compiler does not yet handle (capture-lexical-environment) > (which uses a new tree-il type). > > * The lexical environment object is currently non-opaque list structure. > > * I deliberately avoided reindenting eval.scm so that the non-whitespace > changes would be evident, to make review easier. > > * I wouldn't be surprised if `primitive-local-eval' does the wrong thing > if (current-module) is different from what it was when the associated > `primitive-eval' was called. I don't know what the right thing would be (the simplest would likely be to just save the current module along with the rest) and in my previous use of local-eval, I had no idea what would happen in this case. But since my application seemed to work as expected (likely because of not changing modules), I left worrying about this alone. -- David Kastrup ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: [PATCH] Implement `capture-lexical-environment' in evaluator 2011-12-14 8:48 ` [PATCH] Implement `capture-lexical-environment' in evaluator Mark H Weaver 2011-12-14 9:08 ` David Kastrup @ 2011-12-14 9:36 ` Mark H Weaver 2011-12-16 9:21 ` [PATCH] Implement `the-environment' and `local-eval' " Mark H Weaver 2 siblings, 0 replies; 82+ messages in thread From: Mark H Weaver @ 2011-12-14 9:36 UTC (permalink / raw) To: Noah Lavine; +Cc: Andy Wingo, David Kastrup, guile-devel Another note: I realize that I shouldn't use `expand-top-sequence' to restart the expander within a local lexical environment. At first glance, I guess I should be using `expand' instead. Another possibility: if the (capture-lexical-environment) was found within a body or sequence where local definitions are permitted, restart using the appropriate expansion function that allows local definitions. However, I haven't yet determined whether this is feasible given the way psyntax handles bodies. For that matter, I haven't decided whether this would be desirable. Anyway, now I must sleep. Best, Mark ^ permalink raw reply [flat|nested] 82+ messages in thread
* [PATCH] Implement `the-environment' and `local-eval' in evaluator 2011-12-14 8:48 ` [PATCH] Implement `capture-lexical-environment' in evaluator Mark H Weaver 2011-12-14 9:08 ` David Kastrup 2011-12-14 9:36 ` Mark H Weaver @ 2011-12-16 9:21 ` Mark H Weaver 2011-12-16 9:32 ` David Kastrup 2 siblings, 1 reply; 82+ messages in thread From: Mark H Weaver @ 2011-12-16 9:21 UTC (permalink / raw) To: guile-devel [-- Attachment #1: Type: text/plain, Size: 2369 bytes --] Here's an improved version of the preliminary evaluator-only implementation of `the-environment' and `local-eval'. I renamed the primitives to the Guile 1.8 names, fixed the expansion within `local-eval' to use `expand' instead of `expand-top-sequence', made the module handling more robust, and various other minor improvements. I plan to fully support these primitives in the compiler as well, in a future version of this patch. This is still a _preliminary_ patch. In particular: * The compiler currently fails ungracefully if it encounters (the-environment). * The lexical environment object is currently non-opaque list structure. * I still wouldn't be surprised if `local-eval' does the wrong thing if (current-module) is different from what it was when the associated `primitive-eval' was called. * I manually removed the psyntax-pp.scm patch from the output of git-format-patch (though the header change summary still mentions it), since it was so huge. I guess you'll need to manually regenerate that file yourself, since the Makefiles don't do it automatically: cd guile/module; make ice-9/psyntax-pp.scm.gen Here's an example session: mhw:~/guile$ meta/guile GNU Guile 2.0.3.72-c6748 Copyright (C) 1995-2011 Free Software Foundation, Inc. Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'. This program is free software, and you are welcome to redistribute it under certain conditions; type `,show c' for details. Enter `,help' for help. scheme@(guile-user)> (define env1 (primitive-eval '(let-syntax ((foo (syntax-rules () ((foo x) (quote x))))) (let ((x 1) (y 2)) (the-environment))))) scheme@(guile-user)> (local-eval 'x env1) $1 = 1 scheme@(guile-user)> (local-eval 'y env1) $2 = 2 scheme@(guile-user)> (local-eval '(foo (1 2)) env1) $3 = (1 2) scheme@(guile-user)> (define env2 (local-eval '(let-syntax ((bar (syntax-rules () ((bar x) (foo x))))) (let ((x 1) (z 3)) (the-environment))) env1)) scheme@(guile-user)> (local-eval 'x env2) $4 = 1 scheme@(guile-user)> (local-eval '(bar (1 2)) env2) $5 = (1 2) scheme@(guile-user)> (local-eval '(foo (1 2)) env2) $6 = (1 2) scheme@(guile-user)> (local-eval 'z env2) $7 = 3 scheme@(guile-user)> (local-eval '(set! x (+ x 10)) env2) $8 = 11 scheme@(guile-user)> (local-eval 'x env1) $9 = 1 Mark [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: Implement `the-environment' and `local-eval' in evaluator --] [-- Type: text/x-patch, Size: 12173 bytes --] From c6748349a833cd61b380259ca8b9d81d7f14128f Mon Sep 17 00:00:00 2001 From: Mark H Weaver <mhw@netris.org> Date: Wed, 14 Dec 2011 03:12:43 -0500 Subject: [PATCH] Implement `the-environment' and `local-eval' in evaluator PRELIMINARY WORK, not ready for commit. --- libguile/expand.c | 5 + libguile/expand.h | 13 + libguile/memoize.c | 18 + libguile/memoize.h | 5 +- module/ice-9/eval.scm | 31 + module/ice-9/psyntax-pp.scm |23299 ++++++++++++++++++++++--------------------- module/ice-9/psyntax.scm | 26 +- module/language/tree-il.scm | 8 + 8 files changed, 12095 insertions(+), 11310 deletions(-) diff --git a/module/ice-9/eval.scm b/module/ice-9/eval.scm index c0fa64c..7d6e6c1 100644 --- a/module/ice-9/eval.scm +++ b/module/ice-9/eval.scm @@ -213,6 +213,8 @@ ;;; `eval' in this order, to put the most frequent cases first. ;;; +(define local-eval #f) ;; This is set! from within the primitive-eval block + (define primitive-eval (let () ;; We pre-generate procedures with fixed arities, up to some number of @@ -357,6 +359,14 @@ ;; Finally, eval the body. (eval body env))))))))))))))) + ;; FIXME: make this opaque!! + (define (make-lexical-environment module eval-env memoizer-env expander-env) + (list '<lexical-environment> module eval-env memoizer-env expander-env)) + (define lexical-environment:module cadr) + (define lexical-environment:eval-env caddr) + (define lexical-environment:memoizer-env cadddr) + (define (lexical-environment:expander-env env) (car (cddddr env))) + ;; The "engine". EXP is a memoized expression. (define (eval exp env) (memoized-expression-case exp @@ -459,6 +469,12 @@ (eval exp env) (eval handler env))) + (('the-environment (memoizer-env . expander-env)) + (let ((module (capture-env (if (pair? env) + (cdr (last-pair env)) + env)))) + (make-lexical-environment module env memoizer-env expander-env))) + (('call/cc proc) (call/cc (eval proc env))) @@ -468,6 +484,21 @@ var-or-spec (memoize-variable-access! exp #f)) (eval x env))))) + + (set! local-eval + (lambda (exp env) + "Evaluate @var{exp} within the lexical environment @var{env}." + (let ((module (lexical-environment:module env)) + (eval-env (lexical-environment:eval-env env)) + (memoizer-env (lexical-environment:memoizer-env env)) + (expander-env (lexical-environment:expander-env env))) + (eval (memoize-local-expression + (if (macroexpanded? exp) + exp + ((module-transformer module) + exp #:env expander-env)) + memoizer-env) + eval-env)))) ;; primitive-eval (lambda (exp) diff --git a/libguile/memoize.h b/libguile/memoize.h index 26bd5b1..f012d3a 100644 --- a/libguile/memoize.h +++ b/libguile/memoize.h @@ -44,6 +44,7 @@ SCM_API SCM scm_sym_quote; SCM_API SCM scm_sym_quasiquote; SCM_API SCM scm_sym_unquote; SCM_API SCM scm_sym_uq_splicing; +SCM_API SCM scm_sym_the_environment; SCM_API SCM scm_sym_with_fluids; SCM_API SCM scm_sym_at; @@ -90,13 +91,15 @@ enum SCM_M_TOPLEVEL_SET, SCM_M_MODULE_REF, SCM_M_MODULE_SET, - SCM_M_PROMPT + SCM_M_PROMPT, + SCM_M_THE_ENVIRONMENT }; \f SCM_INTERNAL SCM scm_memoize_expression (SCM exp); +SCM_INTERNAL SCM scm_memoize_local_expression (SCM exp, SCM memoizer_env); SCM_INTERNAL SCM scm_unmemoize_expression (SCM memoized); SCM_INTERNAL SCM scm_memoized_expression_typecode (SCM memoized); SCM_INTERNAL SCM scm_memoized_expression_data (SCM memoized); diff --git a/libguile/memoize.c b/libguile/memoize.c index 911d972..f7be46e 100644 --- a/libguile/memoize.c +++ b/libguile/memoize.c @@ -112,6 +112,8 @@ scm_t_bits scm_tc16_memoized; MAKMEMO (SCM_M_MODULE_SET, scm_cons (val, scm_cons (mod, scm_cons (var, public)))) #define MAKMEMO_PROMPT(tag, exp, handler) \ MAKMEMO (SCM_M_PROMPT, scm_cons (tag, scm_cons (exp, handler))) +#define MAKMEMO_THE_ENVIRONMENT(memoizer_env, expander_env) \ + MAKMEMO (SCM_M_THE_ENVIRONMENT, scm_cons(memoizer_env, expander_env)) /* Primitives for the evaluator */ @@ -143,6 +145,7 @@ static const char *const memoized_tags[] = "module-ref", "module-set!", "prompt", + "the-environment", }; static int @@ -426,6 +429,9 @@ memoize (SCM exp, SCM env) memoize_exps (REF (exp, DYNLET, VALS), env), memoize (REF (exp, DYNLET, BODY), env)); + case SCM_EXPANDED_THE_ENVIRONMENT: + return MAKMEMO_THE_ENVIRONMENT (env, REF (exp, THE_ENVIRONMENT, EXPANDER_ENV)); + default: abort (); } @@ -444,6 +450,16 @@ SCM_DEFINE (scm_memoize_expression, "memoize-expression", 1, 0, 0, } #undef FUNC_NAME +SCM_DEFINE (scm_memoize_local_expression, "memoize-local-expression", 2, 0, 0, + (SCM exp, SCM memoizer_env), + "Memoize the expression @var{exp} within @var{memoizer_env}.") +#define FUNC_NAME s_scm_memoize_local_expression +{ + SCM_ASSERT_TYPE (SCM_EXPANDED_P (exp), exp, 1, FUNC_NAME, "expanded"); + return memoize (exp, memoizer_env); +} +#undef FUNC_NAME + \f @@ -706,6 +722,8 @@ unmemoize (const SCM expr) unmemoize (CAR (args)), unmemoize (CADR (args)), unmemoize (CDDR (args))); + case SCM_M_THE_ENVIRONMENT: + return scm_list_3 (scm_sym_the_environment, CAR (args), CDR (args)); default: abort (); } diff --git a/libguile/expand.h b/libguile/expand.h index 02e6e17..b150058 100644 --- a/libguile/expand.h +++ b/libguile/expand.h @@ -54,6 +54,7 @@ typedef enum SCM_EXPANDED_LET, SCM_EXPANDED_LETREC, SCM_EXPANDED_DYNLET, + SCM_EXPANDED_THE_ENVIRONMENT, SCM_NUM_EXPANDED_TYPES, } scm_t_expanded_type; @@ -330,6 +331,18 @@ enum #define SCM_MAKE_EXPANDED_DYNLET(src, fluids, vals, body) \ scm_c_make_struct (exp_vtables[SCM_EXPANDED_DYNLET], 0, SCM_NUM_EXPANDED_DYNLET_FIELDS, SCM_UNPACK (src), SCM_UNPACK (fluids), SCM_UNPACK (vals), SCM_UNPACK (body)) +#define SCM_EXPANDED_THE_ENVIRONMENT_TYPE_NAME "the-environment" +#define SCM_EXPANDED_THE_ENVIRONMENT_FIELD_NAMES \ + { "src", "expander-env", } +enum + { + SCM_EXPANDED_THE_ENVIRONMENT_SRC, + SCM_EXPANDED_THE_ENVIRONMENT_EXPANDER_ENV, + SCM_NUM_EXPANDED_THE_ENVIRONMENT_FIELDS, + }; +#define SCM_MAKE_EXPANDED_THE_ENVIRONMENT(src, expander_env) \ + scm_c_make_struct (exp_vtables[SCM_EXPANDED_THE_ENVIRONMENT], 0, SCM_NUM_EXPANDED_THE_ENVIRONMENT_FIELDS, SCM_UNPACK (src), SCM_UNPACK (expander_env)) + #endif /* BUILDING_LIBGUILE */ \f diff --git a/libguile/expand.c b/libguile/expand.c index bdecd80..18d9e40 100644 --- a/libguile/expand.c +++ b/libguile/expand.c @@ -85,6 +85,8 @@ static const char** exp_field_names[SCM_NUM_EXPANDED_TYPES]; SCM_MAKE_EXPANDED_LETREC(src, in_order_p, names, gensyms, vals, body) #define DYNLET(src, fluids, vals, body) \ SCM_MAKE_EXPANDED_DYNLET(src, fluids, vals, body) +#define THE_ENVIRONMENT(src, expander_env) \ + SCM_MAKE_EXPANDED_THE_ENVIRONMENT(src, expander_env) #define CAR(x) SCM_CAR(x) #define CDR(x) SCM_CDR(x) @@ -203,6 +205,8 @@ SCM_GLOBAL_SYMBOL (scm_sym_unquote, "unquote"); SCM_GLOBAL_SYMBOL (scm_sym_quasiquote, "quasiquote"); SCM_GLOBAL_SYMBOL (scm_sym_uq_splicing, "unquote-splicing"); +SCM_GLOBAL_SYMBOL (scm_sym_the_environment, "the-environment"); + SCM_KEYWORD (kw_allow_other_keys, "allow-other-keys"); SCM_KEYWORD (kw_optional, "optional"); SCM_KEYWORD (kw_key, "key"); @@ -1250,6 +1254,7 @@ scm_init_expand () DEFINE_NAMES (LET); DEFINE_NAMES (LETREC); DEFINE_NAMES (DYNLET); + DEFINE_NAMES (THE_ENVIRONMENT); scm_exp_vtable_vtable = scm_make_vtable (scm_from_locale_string (SCM_VTABLE_BASE_LAYOUT "pwuwpw"), diff --git a/module/language/tree-il.scm b/module/language/tree-il.scm index 1d391c4..907cc82 100644 --- a/module/language/tree-il.scm +++ b/module/language/tree-il.scm @@ -49,6 +49,7 @@ <dynlet> dynlet? make-dynlet dynlet-src dynlet-fluids dynlet-vals dynlet-body <dynref> dynref? make-dynref dynref-src dynref-fluid <dynset> dynset? make-dynset dynset-src dynset-fluid dynset-exp + <the-environment> the-environment? make-the-environment the-environment-src the-environment-expander-env <prompt> prompt? make-prompt prompt-src prompt-tag prompt-body prompt-handler <abort> abort? make-abort abort-src abort-tag abort-args abort-tail @@ -125,6 +126,7 @@ ;; (<let> names gensyms vals body) ;; (<letrec> in-order? names gensyms vals body) ;; (<dynlet> fluids vals body) + ;; (<the-environment> expander-env) (define-type (<tree-il> #:common-slots (src) #:printer print-tree-il) (<fix> names gensyms vals body) @@ -324,6 +326,9 @@ ((<dynset> fluid exp) `(dynset ,(unparse-tree-il fluid) ,(unparse-tree-il exp))) + ((<the-environment> expander-env) + `(the-environment ,expander-env)) + ((<prompt> tag body handler) `(prompt ,(unparse-tree-il tag) ,(unparse-tree-il body) ,(unparse-tree-il handler))) @@ -470,6 +475,9 @@ ((<dynset> fluid exp) `(fluid-set! ,(tree-il->scheme fluid) ,(tree-il->scheme exp))) + ((<the-environment>) + '(the-environment)) + ((<prompt> tag body handler) `(call-with-prompt ,(tree-il->scheme tag) diff --git a/module/ice-9/psyntax.scm b/module/ice-9/psyntax.scm index e522f54..292f932 100644 --- a/module/ice-9/psyntax.scm +++ b/module/ice-9/psyntax.scm @@ -307,6 +307,14 @@ (if (not (assq 'name meta)) (set-lambda-meta! val (acons 'name name meta)))))) + ;; data type for exporting the compile-type environment + ;; FIXME: make this opaque! + (define (make-psyntax-env r w mod) + (list '<psyntax-env> r w mod)) + (define psyntax-env:r cadr) + (define psyntax-env:w caddr) + (define psyntax-env:mod cadddr) + ;; output constructors (define build-void (lambda (source) @@ -410,6 +418,9 @@ (define (build-data src exp) (make-const src exp)) + (define (build-the-environment src expander-env) + (make-the-environment src expander-env)) + (define build-sequence (lambda (src exps) (if (null? (cdr exps)) @@ -1786,6 +1797,13 @@ (_ (syntax-violation 'quote "bad syntax" (source-wrap e w s mod)))))) + (global-extend 'core 'the-environment + (lambda (e r w s mod) + (syntax-case e () + ((_) (build-the-environment s (make-psyntax-env r w mod))) + (_ (syntax-violation 'quote "bad syntax" + (source-wrap e w s mod)))))) + (global-extend 'core 'syntax (let () (define gen-syntax @@ -2395,9 +2413,11 @@ ;; expanded, and the expanded definitions are also residualized into ;; the object file if we are compiling a file. (set! macroexpand - (lambda* (x #:optional (m 'e) (esew '(eval))) - (expand-top-sequence (list x) null-env top-wrap #f m esew - (cons 'hygiene (module-name (current-module)))))) + (lambda* (x #:optional (m 'e) (esew '(eval)) #:key env) + (if env + (expand x (psyntax-env:r env) (psyntax-env:w env) (psyntax-env:mod env)) + (expand-top-sequence (list x) null-env top-wrap #f m esew + (cons 'hygiene (module-name (current-module))))))) (set! identifier? (lambda (x) -- 1.7.5.4 ^ permalink raw reply related [flat|nested] 82+ messages in thread
* Re: [PATCH] Implement `the-environment' and `local-eval' in evaluator 2011-12-16 9:21 ` [PATCH] Implement `the-environment' and `local-eval' " Mark H Weaver @ 2011-12-16 9:32 ` David Kastrup 2011-12-16 14:00 ` Peter TB Brett 0 siblings, 1 reply; 82+ messages in thread From: David Kastrup @ 2011-12-16 9:32 UTC (permalink / raw) To: guile-devel Mark H Weaver <mhw@netris.org> writes: > Here's an improved version of the preliminary evaluator-only > implementation of `the-environment' and `local-eval'. I renamed the > primitives to the Guile 1.8 names, fixed the expansion within > `local-eval' to use `expand' instead of `expand-top-sequence', made the > module handling more robust, and various other minor improvements. > > I plan to fully support these primitives in the compiler as well, in a > future version of this patch. > > This is still a _preliminary_ patch. In particular: > > * The compiler currently fails ungracefully if it encounters > (the-environment). > > * The lexical environment object is currently non-opaque list structure. > > * I still wouldn't be surprised if `local-eval' does the wrong thing if > (current-module) is different from what it was when the associated > `primitive-eval' was called. Before anyone even _defines_ what the "right thing" would be, there is little point in worrying about this. I don't think that `local-eval' 1.8 documented any behavior for this case (well, it did not document any behavior for a lot of cases). So it probably makes sense to look afterwards what will happen without special precautions, and unless that is spectacularly useless or inconsistent, call it the "right thing" by definition. -- David Kastrup ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: [PATCH] Implement `the-environment' and `local-eval' in evaluator 2011-12-16 9:32 ` David Kastrup @ 2011-12-16 14:00 ` Peter TB Brett 2011-12-16 14:26 ` David Kastrup 2011-12-16 15:27 ` Mark H Weaver 0 siblings, 2 replies; 82+ messages in thread From: Peter TB Brett @ 2011-12-16 14:00 UTC (permalink / raw) To: guile-devel David Kastrup <dak@gnu.org> writes: >> * I still wouldn't be surprised if `local-eval' does the wrong thing if >> (current-module) is different from what it was when the associated >> `primitive-eval' was called. > > Before anyone even _defines_ what the "right thing" would be, there is > little point in worrying about this. I don't think that `local-eval' > 1.8 documented any behavior for this case (well, it did not document any > behavior for a lot of cases). > > So it probably makes sense to look afterwards what will happen without > special precautions, and unless that is spectacularly useless or > inconsistent, call it the "right thing" by definition. Maybe it makes even more sense (at this stage) to state that the behaviour in this case is undefined? Peter -- Peter Brett <peter@peter-b.co.uk> Remote Sensing Research Group Surrey Space Centre ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: [PATCH] Implement `the-environment' and `local-eval' in evaluator 2011-12-16 14:00 ` Peter TB Brett @ 2011-12-16 14:26 ` David Kastrup 2011-12-16 15:27 ` Mark H Weaver 1 sibling, 0 replies; 82+ messages in thread From: David Kastrup @ 2011-12-16 14:26 UTC (permalink / raw) To: guile-devel Peter TB Brett <peter@peter-b.co.uk> writes: > David Kastrup <dak@gnu.org> writes: > >>> * I still wouldn't be surprised if `local-eval' does the wrong thing if >>> (current-module) is different from what it was when the associated >>> `primitive-eval' was called. >> >> Before anyone even _defines_ what the "right thing" would be, there is >> little point in worrying about this. I don't think that `local-eval' >> 1.8 documented any behavior for this case (well, it did not document any >> behavior for a lot of cases). >> >> So it probably makes sense to look afterwards what will happen without >> special precautions, and unless that is spectacularly useless or >> inconsistent, call it the "right thing" by definition. > > Maybe it makes even more sense (at this stage) to state that the > behaviour in this case is undefined? In my opinion it does not make sense at this stage to state anything. Declaring the behavior as being undefined is premature when further work might discover that it makes eminent sense to define it in a particular way. -- David Kastrup ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: [PATCH] Implement `the-environment' and `local-eval' in evaluator 2011-12-16 14:00 ` Peter TB Brett 2011-12-16 14:26 ` David Kastrup @ 2011-12-16 15:27 ` Mark H Weaver 2011-12-16 16:01 ` Andy Wingo 2011-12-16 16:59 ` Hans Aberg 1 sibling, 2 replies; 82+ messages in thread From: Mark H Weaver @ 2011-12-16 15:27 UTC (permalink / raw) To: Peter TB Brett; +Cc: guile-devel Peter TB Brett <peter@peter-b.co.uk> writes: > David Kastrup <dak@gnu.org> writes: > >>> * I still wouldn't be surprised if `local-eval' does the wrong thing if >>> (current-module) is different from what it was when the associated >>> `primitive-eval' was called. >> >> Before anyone even _defines_ what the "right thing" would be, there is >> little point in worrying about this. I don't think that `local-eval' >> 1.8 documented any behavior for this case (well, it did not document any >> behavior for a lot of cases). >> >> So it probably makes sense to look afterwards what will happen without >> special precautions, and unless that is spectacularly useless or >> inconsistent, call it the "right thing" by definition. > > Maybe it makes even more sense (at this stage) to state that the > behaviour in this case is undefined? To my mind, top-level (module) variables are conceptually part of every lexical environment placed within that module. For example, code like this: (define (factorial x) (if (zero? x) 1 (* x (factorial (- x 1))))) should act as one would naturally expect (assuming that factorial is not later set!), whether it is placed within a local block or placed at the top-level of some module. It would be crazy for any lexical environment "search path" starting from within factorial's definition block to lead to a different module than the one where `factorial' was defined. In other words, in the following example, any variable lookup that would lead to `foo2' should also lead to the `foo1' (assuming it's not shadowed by a lexical binding). (define foo1 #f) (let ((foo2 #f)) <insert any code here>) As an interesting case, suppose that you define the following macro in module A: (define foo 'module-a) (define-syntax alt-environment (syntax-rules () ((_) (the-environment)))) and then evaluate the following within module B: (define foo 'module-b) (local-eval 'foo (alt-environment)) What should the result be? My guess is that it should return 'module-a, because I think conceptually it should act as though the local-expression passed to `local-eval' were put in place of (the-environment), wherever that might be. Thoughts? Mark ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: [PATCH] Implement `the-environment' and `local-eval' in evaluator 2011-12-16 15:27 ` Mark H Weaver @ 2011-12-16 16:01 ` Andy Wingo 2011-12-16 17:44 ` Mark H Weaver 2011-12-16 16:59 ` Hans Aberg 1 sibling, 1 reply; 82+ messages in thread From: Andy Wingo @ 2011-12-16 16:01 UTC (permalink / raw) To: Mark H Weaver; +Cc: Peter TB Brett, guile-devel On Fri 16 Dec 2011 16:27, Mark H Weaver <mhw@netris.org> writes: > To my mind, top-level (module) variables are conceptually part of every > lexical environment placed within that module. Agreed. > (define foo 'module-a) > (define-syntax alt-environment > (syntax-rules () > ((_) (the-environment)))) > and then evaluate the following within module B: > (define foo 'module-b) > (local-eval 'foo (alt-environment)) > What should the result be? > My guess is that it should return 'module-a, because I think > conceptually it should act as though the local-expression passed to > `local-eval' were put in place of (the-environment), wherever that Dunno, I could make an argument either way :) Another question is how would local environments relate to procedural macros. Andy -- http://wingolog.org/ ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: [PATCH] Implement `the-environment' and `local-eval' in evaluator 2011-12-16 16:01 ` Andy Wingo @ 2011-12-16 17:44 ` Mark H Weaver 2011-12-16 19:12 ` Mark H Weaver 2012-01-07 1:18 ` Andy Wingo 0 siblings, 2 replies; 82+ messages in thread From: Mark H Weaver @ 2011-12-16 17:44 UTC (permalink / raw) To: Andy Wingo; +Cc: Peter TB Brett, guile-devel Andy Wingo <wingo@pobox.com> writes: >> (define foo 'module-a) >> (define-syntax alt-environment >> (syntax-rules () >> ((_) (the-environment)))) > >> and then evaluate the following within module B: > >> (define foo 'module-b) >> (local-eval 'foo (alt-environment)) > >> What should the result be? > >> My guess is that it should return 'module-a, because I think >> conceptually it should act as though the local-expression passed to >> `local-eval' were put in place of (the-environment), wherever that > > Dunno, I could make an argument either way :) Having thought more about this, I'm fully convinced that 'module-a should be the answer. The real reason is that we need to macroexpand the new local expression within the expander-environment captured by (the-environment), and this expander-environment really determines the lexical environment that symbols are looked up in, in the same way that free variable references from a macro definition are resolved in the lexical environment of the macro definition instead of the place where the macro is used. If we don't use the expander-environment captured by (the-environment), what other expander-environment would we use, and how would we capture this other environment? I guess the other option would be that, if (the-environment) is found within a macro definition, then we should instead use the expander environment from where the currently-expanding macro was _used_. But what if that use is itself within another macro definition? I don't think it makes sense to just go up one level, that seems very inelegant. No, it should be one extreme or the other, so the other logical choice would be to go all the way up the macro expansion stack to the top, where the use of the _initial_ macro was found. But regardless of whether we go up one level or all the way to the top, this other option would cause serious problems. In the general case, (the-environment) could be nested quite deeply within `let's, `let-syntax's, and worse of all, `lambda's! For example, consider the following example: (define-syntax blah (syntax-rules () ((_) (lambda () (let-syntax (...) (let (<lots of variable definitions>) <lots of code> (the-environment))))))) If we were to use this crazy alternate rule, then `local-eval' should evaluate its expression in the environment where the above macro was used, i.e. where the procedure was _defined_. I hope we can all agree that this would be madness. No, the only sane option is to do the straightforward thing: we use the expander environment captured by (the-environment), even if that's found within a macro. And that also means that the module used by `local-eval' should be the module found within the expander environment, i.e. the module where the macro containing (the-environment) was _defined_. Does this make sense? Best, Mark ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: [PATCH] Implement `the-environment' and `local-eval' in evaluator 2011-12-16 17:44 ` Mark H Weaver @ 2011-12-16 19:12 ` Mark H Weaver 2012-01-07 1:26 ` Andy Wingo 2012-01-07 1:18 ` Andy Wingo 1 sibling, 1 reply; 82+ messages in thread From: Mark H Weaver @ 2011-12-16 19:12 UTC (permalink / raw) To: Andy Wingo; +Cc: Peter TB Brett, guile-devel I wrote: > Having thought more about this, I'm fully convinced that 'module-a > should be the answer. On second thought, I don't think my argument was very solid. My nephew demanded my attention while I was working on that email, and so I rushed it out when I should have put it aside and pondered some more. So, in cases where (the-environment) is found within a macro template, I guess I'm still undecided about whether the captured expander environment should be at the point of (the-environment), or whether it should be at the point of the initial macro use that led to that expansion. However, I'm fairly sure that those are the only two sane options, and I feel quite certain that the module used by `local-eval' should be taken from the captured expander environment, whatever that may be. Do people agree with that those are the two sane options? Any thoughts on which one is more useful? I guess we should try to come up with examples of useful macros that use (the-environment), and hope that these examples point to a clear winner. For that matter, in the unlikely case that both options seem genuinely useful, it wouldn't be hard to implement both, since they would both translate to the same `tree-il' type. The only difference would be in the expander, since it puts the <expander-environment> into the tree-il node, and that piece of code is only a few lines. Thoughts? Best, Mark ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: [PATCH] Implement `the-environment' and `local-eval' in evaluator 2011-12-16 19:12 ` Mark H Weaver @ 2012-01-07 1:26 ` Andy Wingo 2012-01-07 17:30 ` Mark H Weaver 0 siblings, 1 reply; 82+ messages in thread From: Andy Wingo @ 2012-01-07 1:26 UTC (permalink / raw) To: Mark H Weaver; +Cc: Peter TB Brett, guile-devel On Fri 16 Dec 2011 20:12, Mark H Weaver <mhw@netris.org> writes: > So, in cases where (the-environment) is found within a macro template, > I guess I'm still undecided about whether the captured expander > environment should be at the point of (the-environment), or whether it > should be at the point of the initial macro use that led to that > expansion. Here's the thing, I think: FOO and (local-eval FOO (the-environment)) should be equivalent. These two are the same: (library (a) (export fetch-a) (import (guile)) (define a 42) (define-syntax fetch-a (syntax-rules () ((_) a)))) (library (a) (export fetch-a) (import (guile)) (define a 42) (define-syntax fetch-a (syntax-rules () ((_) (local-eval 'a (the-environment)))))) So I think that the environment should be captured such that these semantics are followed. I think that means the former, of your two options. Andy -- http://wingolog.org/ ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: [PATCH] Implement `the-environment' and `local-eval' in evaluator 2012-01-07 1:26 ` Andy Wingo @ 2012-01-07 17:30 ` Mark H Weaver 0 siblings, 0 replies; 82+ messages in thread From: Mark H Weaver @ 2012-01-07 17:30 UTC (permalink / raw) To: Andy Wingo; +Cc: guile-devel Andy Wingo <wingo@pobox.com> writes: > Here's the thing, I think: FOO and (local-eval FOO (the-environment)) > should be equivalent. Agreed. This is the equivalence that we should strive to achieve. My simple patch honors this equivalence for the bindings that it supports (which unfortunately does not yet include local syntax or pattern variables). Note that in the presence of locally-bound procedural macros, I see no good way to achieve this equivalence (in the general case) without the ability to embed references to the transformer procedures within compiled code. Thanks, Mark ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: [PATCH] Implement `the-environment' and `local-eval' in evaluator 2011-12-16 17:44 ` Mark H Weaver 2011-12-16 19:12 ` Mark H Weaver @ 2012-01-07 1:18 ` Andy Wingo 1 sibling, 0 replies; 82+ messages in thread From: Andy Wingo @ 2012-01-07 1:18 UTC (permalink / raw) To: Mark H Weaver; +Cc: Peter TB Brett, guile-devel On Fri 16 Dec 2011 18:44, Mark H Weaver <mhw@netris.org> writes: > Andy Wingo <wingo@pobox.com> writes: >>> (define foo 'module-a) >>> (define-syntax alt-environment >>> (syntax-rules () >>> ((_) (the-environment)))) >> >>> and then evaluate the following within module B: >> >>> (define foo 'module-b) >>> (local-eval 'foo (alt-environment)) >> >>> What should the result be? > > Having thought more about this, I'm fully convinced that 'module-a > should be the answer. Me too. Andy -- http://wingolog.org/ ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: [PATCH] Implement `the-environment' and `local-eval' in evaluator 2011-12-16 15:27 ` Mark H Weaver 2011-12-16 16:01 ` Andy Wingo @ 2011-12-16 16:59 ` Hans Aberg 1 sibling, 0 replies; 82+ messages in thread From: Hans Aberg @ 2011-12-16 16:59 UTC (permalink / raw) To: Mark H Weaver; +Cc: Peter TB Brett, guile-devel On 16 Dec 2011, at 16:27, Mark H Weaver wrote: > As an interesting case, suppose that you define the following macro in > module A: > > (define foo 'module-a) > (define-syntax alt-environment > (syntax-rules () > ((_) (the-environment)))) > > and then evaluate the following within module B: > > (define foo 'module-b) > (local-eval 'foo (alt-environment)) > > What should the result be? > > My guess is that it should return 'module-a, because I think > conceptually it should act as though the local-expression passed to > `local-eval' were put in place of (the-environment), wherever that > might be. > > Thoughts? I thought it should be a way to capture the environment when (the-environment) is evaluated, returning a reference to it. So (define foo 'module-a) (define bar (the-environment)) ; Capture environment, and save reference in bar. Now in (define foo 'module-b) (local-eval 'foo bar) bar refers to the captured environment and 'foo is inserted into that; that is 'module-a. It would need to capture all dynamic syntactic rules as well, otherwise the code cannot be run safely. Hans ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-14 7:50 ` Mark H Weaver 2011-12-14 8:48 ` [PATCH] Implement `capture-lexical-environment' in evaluator Mark H Weaver @ 2011-12-14 10:08 ` Andy Wingo 2011-12-14 10:27 ` David Kastrup 2011-12-14 11:03 ` Mark H Weaver 1 sibling, 2 replies; 82+ messages in thread From: Andy Wingo @ 2011-12-14 10:08 UTC (permalink / raw) To: Mark H Weaver; +Cc: David Kastrup, guile-devel On Wed 14 Dec 2011 08:50, Mark H Weaver <mhw@netris.org> writes: > I have successfully implemented the (capture-lexical-environment) > special form in the evaluator, and also primitive-local-eval. I dunno, Mark. That's a neat hack, but why should we have it in Guile? It can't compile. It's not for efficiency, because if you wanted more efficiency, you would compile. So what is it for? It has a cost, so it needs to justify itself. Andy -- http://wingolog.org/ ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-14 10:08 ` Anything better for delayed lexical evaluation than (lambda () ...)? Andy Wingo @ 2011-12-14 10:27 ` David Kastrup 2011-12-14 13:35 ` Andy Wingo 2011-12-14 11:03 ` Mark H Weaver 1 sibling, 1 reply; 82+ messages in thread From: David Kastrup @ 2011-12-14 10:27 UTC (permalink / raw) To: Andy Wingo; +Cc: Mark H Weaver, guile-devel Andy Wingo <wingo@pobox.com> writes: > On Wed 14 Dec 2011 08:50, Mark H Weaver <mhw@netris.org> writes: > >> I have successfully implemented the (capture-lexical-environment) >> special form in the evaluator, and also primitive-local-eval. > > I dunno, Mark. That's a neat hack, but why should we have it in Guile? > It can't compile. It's not for efficiency, because if you wanted more > efficiency, you would compile. Most of the multi-list commands in srfi-1 have awful performance implications once you give them more than a single list. And you can usually create more efficient equivalent code manually for most non-generic uses of them. So they are obviously not in there for efficiency, but rather feature completeness and user friendliness. > So what is it for? It has a cost, so it needs to justify itself. It is a trade-off. The reason to include it in Guile rather than providing it as an external package is because it depends on internals of Guile, and thus needs to be maintained along with it. Is the expected cost of maintenance worth the gain in Guile's attractiveness? A more thorough solution that would actually cooperate with the compiler would likely be more attractive, and more prone to maintenance. But it would also likely serve to hold open the architecture for future developments or plugins that could be interesting for integrating Guile into other language systems like Emacs Lisp (which is only very slowly moving towards lexical bindings) or Lua or other binding-intensive systems. Language interfaces that are not strictly ad-hoc and closed-door would certainly help in keeping Guile interesting for applications where the respective maintainers don't have to count on the continued cooperation of Guile developers, but merely on the continued existence of published interfaces. I can't judge the best trade-off point here. Nor how complex it would be to actually work with the compiler, and be reasonably confident that it will continue to work without too much hassle in future. But an outside package meddling with undocumented internals is not safe to rely on at all. It can break at any point of time with no active Guile developer being in a position where he would feel compelled to fix it for the sake of Guile. -- David Kastrup ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-14 10:27 ` David Kastrup @ 2011-12-14 13:35 ` Andy Wingo 2011-12-14 15:21 ` David Kastrup 2011-12-14 17:26 ` Mark H Weaver 0 siblings, 2 replies; 82+ messages in thread From: Andy Wingo @ 2011-12-14 13:35 UTC (permalink / raw) To: David Kastrup; +Cc: Mark H Weaver, guile-devel Hi David, On Wed 14 Dec 2011 11:27, David Kastrup <dak@gnu.org> writes: > Andy Wingo <wingo@pobox.com> writes: > >> On Wed 14 Dec 2011 08:50, Mark H Weaver <mhw@netris.org> writes: >> >>> I have successfully implemented the (capture-lexical-environment) >>> special form in the evaluator, and also primitive-local-eval. Let's call it `(the-environment)', as it is what it was called in Guile 1.8. >> why should we have it in Guile? > > feature completeness and user friendliness. OK, so we are not arguing for efficiency then. (That's fine, but we should be clear about it.) > Emacs Lisp or Lua or other binding-intensive systems. `the-environment' is not useful for Elisp or Lua. We already have an OK Elisp implementation, and it will be quite good once bipt merges his branch. It is implemented as a compiler to tree-il. Likewise there is a branch for Lua. It has some issues, but not unsolveable, and not related to this question. It is also implemented as a compiler to tree-il. > Language interfaces that are not strictly ad-hoc and closed-door would > certainly help in keeping Guile interesting for applications where the > respective maintainers don't have to count on the continued > cooperation of Guile developers, but merely on the continued existence > of published interfaces. Indeed. > But an outside package meddling with undocumented internals is not safe > to rely on at all. It can break at any point of time with no active > Guile developer being in a position where he would feel compelled to fix > it for the sake of Guile. I agree. I agree also that Guile should do its best to be good for lilypond. It is my opinion -- and I could be wrong here, either by misunderstanding (again!) the issues, or for whatever reason -- that closures are the best solution to the #{#} problem. Reasons: * Lambda is standard, well-understood, and general. * Using `lambda' does not preclude compilation. * Using closures does not require any additional work on the part of Guile. (This is only partly selfish; it also decreases the amount of coupling of lilypond to any particular Guile version.) * Allows for integration with the debugger, in 2.0. (Currently, interpreted code does not debug well.) * Potentially more efficient, and will benefit from future speed improvements in Guile. WDYT? Andy -- http://wingolog.org/ ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-14 13:35 ` Andy Wingo @ 2011-12-14 15:21 ` David Kastrup 2011-12-14 15:55 ` Andy Wingo 2011-12-14 17:26 ` Mark H Weaver 1 sibling, 1 reply; 82+ messages in thread From: David Kastrup @ 2011-12-14 15:21 UTC (permalink / raw) To: guile-devel Andy Wingo <wingo@pobox.com> writes: > It is my opinion -- and I could be wrong here, either by > misunderstanding (again!) the issues, or for whatever reason -- that > closures are the best solution to the #{#} problem. > > Reasons: > > * Lambda is standard, well-understood, and general. Well, if they are standard, what benefits does using GUILE give us? > * Using `lambda' does not preclude compilation. > > * Using closures does not require any additional work on the part of > Guile. (This is only partly selfish; it also decreases the amount > of coupling of lilypond to any particular Guile version.) Any particular Scheme version. > * Allows for integration with the debugger, in 2.0. (Currently, > interpreted code does not debug well.) Sounds like putting the cart before the horse. After all, interpretative environments offer a lot of potential to be _better_ for debugging than compiled code. One should not lightly waste this. In particular since macro expansion is basically an interpretative operation and macros are among the nastiest things to debug. > * Potentially more efficient, and will benefit from future speed > improvements in Guile. > > WDYT? Well, take a look at the following: dak@lola:/usr/local/tmp/lilypond$ out/bin/lilypond scheme-sandbox GNU LilyPond 2.15.22 Processing `/usr/local/tmp/lilypond/out/share/lilypond/current/ly/scheme-sandbox.ly' Parsing... guile> '#{ ##{ \displayLilyMusic $p #} #} (#<procedure embedded-lilypond (parser lily-string filename line closures)> parser " ##{ \\displayLilyMusic $p #} " #f 0 (list (cons 2 (lambda () (#<procedure embedded-lilypond (parser lily-string filename line closures)> parser " \\displayLilyMusic $p " #f 0 (list (cons 20 (lambda () p)))))))) guile> This case of mutual nesting is not all that outlandish, and the nested lambdas, while managing to carry the value of p from the current lexical environment to the inner layer, are not all that pretty. This could be just (#<procedure embedded-lilypond (parser lily-string filename line closures)> parser " ##{ \\displayLilyMusic $p #} " #f 0 (get-environment)) instead. In short, that I have managed to pick out the nicest prybar does not mean that a lockpick would not be preferable. And even if (or rather: particularly when) we were working like the GUILE gods have intended it, we would be forced to transform every case of nested expressions into one large humongous block compiled at once in order to get lexical nesting (possibly by working with macros which don't exactly make things easier to track), and that would be quite counterproductive to getting useful error messages whenever something in the course of this nesting would go wrong. So even though we can workaround the current situation well enough for now, I think that GUILE might be selling itself under value when compared to its competitors, possibly without compelling need when I look at the sketches of Mark and the discussion of them. -- David Kastrup ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-14 15:21 ` David Kastrup @ 2011-12-14 15:55 ` Andy Wingo 0 siblings, 0 replies; 82+ messages in thread From: Andy Wingo @ 2011-12-14 15:55 UTC (permalink / raw) To: David Kastrup; +Cc: guile-devel On Wed 14 Dec 2011 16:21, David Kastrup <dak@gnu.org> writes: > Andy Wingo <wingo@pobox.com> writes: > >> It is my opinion -- and I could be wrong here, either by >> misunderstanding (again!) the issues, or for whatever reason -- that >> closures are the best solution to the #{#} problem. >> >> Reasons: >> >> * Lambda is standard, well-understood, and general. > > Well, if they are standard, what benefits does using GUILE give us? I'm not sure if this is a rhetorical question or not :), but: 1) Guile has a C library, with C interfaces to its functionality.. 2) Guile is a GNU project. 3) You're already using Guile features (modules, among them). But TBH I don't understand this argument. Lambda is fantastic, and even if Guile provided other facilities, I would still use it. It is the right tool for the job, IMO. >> * Using `lambda' does not preclude compilation. >> >> * Using closures does not require any additional work on the part of >> Guile. (This is only partly selfish; it also decreases the amount >> of coupling of lilypond to any particular Guile version.) > > Any particular Scheme version. This is a great thing. We share the knowledge and in some times the implementations from other Schemes. And there are quite a number of fine Scheme implementations; if you had to switch, for whatever reason, you could. >> * Allows for integration with the debugger, in 2.0. (Currently, >> interpreted code does not debug well.) > > Sounds like putting the cart before the horse. After all, > interpretative environments offer a lot of potential to be _better_ for > debugging than compiled code. I am saying this as the person who wrote the current debugger. > One should not lightly waste this. In > particular since macro expansion is basically an interpretative > operation and macros are among the nastiest things to debug. Very few languages have proper macros. Very few Scheme implementations have macro debuggers. Racket is the only one I know. It has a separate debugger for use at syntax expansion time. I am saying this as the person who maintains the current macro expander. :) >> * Potentially more efficient, and will benefit from future speed >> improvements in Guile. > > Well, take a look at the following: > > dak@lola:/usr/local/tmp/lilypond$ out/bin/lilypond scheme-sandbox > GNU LilyPond 2.15.22 > Processing `/usr/local/tmp/lilypond/out/share/lilypond/current/ly/scheme-sandbox.ly' > Parsing... > guile> '#{ ##{ \displayLilyMusic $p #} #} > (#<procedure embedded-lilypond (parser lily-string filename line closures)> parser " ##{ \\displayLilyMusic $p #} " #f 0 (list (cons 2 (lambda () (#<procedure embedded-lilypond (parser lily-string filename line closures)> parser " \\displayLilyMusic $p " #f 0 (list (cons 20 (lambda () p)))))))) It's not what you see, it's what you don't see :) In Guile 1.8, you don't see: * (Lazy) memoization. (A 1.8 implementation technique.) * Consing up procedure arguments. * Consing up lexicals. * Looking up lexicals. * The evaluator dispatch loop. * Out-of-line calls to primitives. Etc. You are right in saying that it will be slower on 1.8. Oh well. On 2.0 it will probably be faster, and potentially could be much faster -- if it matters. I am sympathetic to your argument. Using closures is more code on your part than using first-class environments. If Guile only provided an interpreter, that would certainly be the way to go. But using closures is not without its advantages as well, as I listed, and it works also with a compiler. Regards, Andy -- http://wingolog.org/ ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-14 13:35 ` Andy Wingo 2011-12-14 15:21 ` David Kastrup @ 2011-12-14 17:26 ` Mark H Weaver 2011-12-14 18:23 ` David Kastrup ` (2 more replies) 1 sibling, 3 replies; 82+ messages in thread From: Mark H Weaver @ 2011-12-14 17:26 UTC (permalink / raw) To: Andy Wingo; +Cc: David Kastrup, guile-devel Andy Wingo <wingo@pobox.com> writes: >>> On Wed 14 Dec 2011 08:50, Mark H Weaver <mhw@netris.org> writes: >>>> I have successfully implemented the (capture-lexical-environment) >>>> special form in the evaluator, and also primitive-local-eval. > > Let's call it `(the-environment)', as it is what it was called in Guile > 1.8. Ah, okay, I didn't know that. It's also a better name :) > It is my opinion -- and I could be wrong here, either by > misunderstanding (again!) the issues, or for whatever reason -- that > closures are the best solution to the #{#} problem. I would love to use closures for this, but they simply are not powerful enough to implement `local-eval' properly. First of all, closures cannot capture the syntactic environment, so things like (let-syntax ((foo ...)) (the-environment)) would not work. Even if you don't use let-syntax, handling macro expansion properly would be difficult using closures. As you know, when you expand (let ((xxx ...)) (the-environment)), the expander replaces `xxx' with a gensym. So `local-eval' needs to somehow map `xxx' to the same gensym. If, on the other hand, you create a closure that instead recognizes the original names found in the source code, then you will break hygiene and cause unintended variable capture problems when macros are even _used_ by the code in question. I think these problems are already serious enough to make the closure method unworkable, but let's suppose that the macro expander wasn't an issue. Even so, there are other problems: How do you build a closure that can be used to evaluate _arbitrary_ Guile code that is not known until after the surrounding code has already been compiled and run? It would apparently involve creating a monster closure that could both access and mutate any lexical variable within its view (ugly, but doable), and then -- and here's the really bad part -- we'd need to create and maintain a `local-eval' that supports the entire Guile language, but is able to handle some (but not all) of its lexical variables being accessible only via these monster closures. IMHO, maintaining this special local evaluator would be a massive maintenance burden. Finally, I'd like to argue that supporting `the-environment' and `local-eval' is really not so bad. Previously, I thought that Lilypond's use of Guile was very intrusive, but now that I understand the situation better, I no longer think so. Lilypond has no need to inspect nor manipulate our lexical environment objects. They only need to be able to pass an opaque environment object from `the-environment' to `local-eval'. This still gives us plenty of freedom to change the representation of these environment objects. The only serious constraint this places on us is that we must keep a full-featured interpreter around. I don't think this is so bad. I love optimizing compilers as much as anyone, but sometimes they are the wrong tool. When efficiency is not a concern, evaluators can provide additional flexibility, as demonstrated by the Lilypond case. Personally, I don't view `the-environment' and `local-eval' as an ugly hack, but rather as a cool feature like delimited continuations. It's something Guile 1.8 could brag about. I'd like for Guile 2.0 to be able to brag about it too :) Mark ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-14 17:26 ` Mark H Weaver @ 2011-12-14 18:23 ` David Kastrup 2011-12-14 18:38 ` Mark H Weaver 2011-12-14 22:56 ` Andy Wingo 2 siblings, 0 replies; 82+ messages in thread From: David Kastrup @ 2011-12-14 18:23 UTC (permalink / raw) To: Mark H Weaver; +Cc: Andy Wingo, guile-devel Mark H Weaver <mhw@netris.org> writes: > Personally, I don't view `the-environment' and `local-eval' as an ugly > hack, but rather as a cool feature like delimited continuations. It's > something Guile 1.8 could brag about. I wish it had bragged about it at all (in the case of the-environment) or more coherently (in the case of procedure-environment and local-eval). Then maybe enough people would have found a use for them that they would not have to rely on me to stand up for them. -- David Kastrup ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-14 17:26 ` Mark H Weaver 2011-12-14 18:23 ` David Kastrup @ 2011-12-14 18:38 ` Mark H Weaver 2011-12-14 19:14 ` David Kastrup 2011-12-14 22:56 ` Andy Wingo 2 siblings, 1 reply; 82+ messages in thread From: Mark H Weaver @ 2011-12-14 18:38 UTC (permalink / raw) To: Andy Wingo; +Cc: David Kastrup, guile-devel Replying to myself: > Andy Wingo <wingo@pobox.com> writes: >> It is my opinion -- and I could be wrong here, either by >> misunderstanding (again!) the issues, or for whatever reason -- that >> closures are the best solution to the #{#} problem. > > I would love to use closures for this, but they simply are not powerful > enough to implement `local-eval' properly. > > First of all, closures cannot capture the syntactic environment, so > things like (let-syntax ((foo ...)) (the-environment)) would not work. This statement (and the rest of my previous email), may have been based on a mistaken impression of your "closure" proposal. I assumed that you meant that (the-environment) should essentially implemented as a macro that expands into a (lambda () ...) that would handle every possible lexical variable access or mutation. However, based on the IRC logs, now I think you may have meant that Lilypond should _scan_ the Lily code between #{ and #} looking for embedded Scheme code, _before_ evaluating anything. If this could be done reliably, it would certainly make our lives easier. Lilypond could create the entire Scheme expression with all the embedded bits of Scheme as closures (not general purpose ones, but with the exact right code within). We could compile the resulting code, and life would be good. I don't know enough about Lilypond's language to know whether it is feasible to do this robustly. We have already established that Lily cannot be parsed without runtime information. Furthermore, their language needs to use lexical tie-ins, which means that the lexer needs contextual information from the parser. Therefore, they cannot even produce a stream of lexical tokens prior to execution. So, with no help from either the parser or scanner, it sounds like they'd be stuck with something like the usual Emacs strategy of scanning source code with regexps to do syntax highlighting: it works in practice most of the time, but it will fail sometimes for unusual inputs. That's fine for a syntax highlighter, but a language implementation should be more robust. Now, once they have found the Scheme bits, how do they go back and parse the Lilypond code? I get that means running something like their existing lexer and parser on ... what exactly? It can't simply the lexer and parser just on the bits in between the Scheme bits, because in general the parser non-terminals can span across one or more of the Scheme bits. I guess they would either have to rescan all of the Scheme code a second time. In summary, it makes my head hurt to even think about how I would implement and maintain Lilypond on Guile 2.0 using this strategy. It shouldn't be this difficult. This case with Lilypond has convinced me that `the-environment' and `local-eval' can be very useful, and sometimes they cannot be easily replaced with our other facilities. Mark ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-14 18:38 ` Mark H Weaver @ 2011-12-14 19:14 ` David Kastrup 2011-12-14 19:44 ` David Kastrup 0 siblings, 1 reply; 82+ messages in thread From: David Kastrup @ 2011-12-14 19:14 UTC (permalink / raw) To: Mark H Weaver; +Cc: Andy Wingo, guile-devel Mark H Weaver <mhw@netris.org> writes: > Replying to myself: > >> Andy Wingo <wingo@pobox.com> writes: >>> It is my opinion -- and I could be wrong here, either by >>> misunderstanding (again!) the issues, or for whatever reason -- that >>> closures are the best solution to the #{#} problem. >> >> I would love to use closures for this, but they simply are not powerful >> enough to implement `local-eval' properly. >> >> First of all, closures cannot capture the syntactic environment, so >> things like (let-syntax ((foo ...)) (the-environment)) would not work. > > This statement (and the rest of my previous email), may have been based > on a mistaken impression of your "closure" proposal. I assumed that you > meant that (the-environment) should essentially implemented as a macro > that expands into a (lambda () ...) that would handle every possible > lexical variable access or mutation. > > However, based on the IRC logs, now I think you may have meant that > Lilypond should _scan_ the Lily code between #{ and #} looking for > embedded Scheme code, _before_ evaluating anything. > > If this could be done reliably, it would certainly make our lives > easier. Lilypond could create the entire Scheme expression with all the > embedded bits of Scheme as closures (not general purpose ones, but with > the exact right code within). We could compile the resulting code, and > life would be good. To be fair: this is what we currently do, and we only actually call those lambdas that end up actually being recognized by the grammar. So as long as (primitive-eval `(lambda () ,(read))) is guaranteed to not ever choke, the potential for error is limited. And to be fair: we already need to use the Scheme reader for skipping stuff behind # and $ or it would be hard to reliably detect the matching #} for the #{ construct when it occurs nested within Scheme again. It took a number of iterations to make all this robust and dependable, and just recently a Lilypond comment of the form % (this can be either #UP or #DOWN) caused trouble since the Scheme reader not just read #DOWN but also ) and then unread it again, making it end up at the start of the _next_ embedded expression for which the Scheme reader was asked for its opinion. > I don't know enough about Lilypond's language to know whether it is > feasible to do this robustly. So-so, but then we don't get around _scanning_ (and thus reading) the potential sexps in a dry run anyway with the current design. This would be different if Lilypond employed a different delimiter strategy. > We have already established that Lily cannot be parsed without runtime > information. Furthermore, their language needs to use lexical > tie-ins, which means that the lexer needs contextual information from > the parser. Therefore, they cannot even produce a stream of lexical > tokens prior to execution. > > So, with no help from either the parser or scanner, it sounds like > they'd be stuck with something like the usual Emacs strategy of > scanning source code with regexps to do syntax highlighting: it works > in practice most of the time, but it will fail sometimes for unusual > inputs. That's fine for a syntax highlighter, but a language > implementation should be more robust. Because of the delimiting issue, there is actually not much of a gain to be expected. Results would improve in cases like #{ % A Lilypond comment before a Scheme element #( #y %) and the comment ends again #} In this case, the current behavior will be that #y has no (lambda ()...) associated with it, and consequently evaluation will be done without a lexical environment, and fail. The old version using local-eval would have worked, but it would still have required the sexp starting at #( to end somewhere before #}. > In summary, it makes my head hurt to even think about how I would > implement and maintain Lilypond on Guile 2.0 using this strategy. I posted a link to the working code already. It made my head hurt. > It shouldn't be this difficult. This case with Lilypond has convinced > me that `the-environment' and `local-eval' can be very useful, and > sometimes they cannot be easily replaced with our other facilities. I think that it is a good thought experiment to assume Lilypond _could_ be properly compiled. To get lexical closure, we would have to bounce _everything_ around in uncompiled or macro form until the top-level form got assembled. For debugging and error messages, that would be inconvenient. Again, to be fair: the current code has to associate file names and line numbers with the string in order to get useful error messages. So it took work getting errors to throw where they belong. So at its current state, Lilypond itself does not much more than lukewarmly ask for local-eval. But for the sake of GUILE, I would consider it a mistake to lose sight of it. It is a powerful tool in the language embedding and self-awareness department. -- David Kastrup ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-14 19:14 ` David Kastrup @ 2011-12-14 19:44 ` David Kastrup 0 siblings, 0 replies; 82+ messages in thread From: David Kastrup @ 2011-12-14 19:44 UTC (permalink / raw) To: guile-devel David Kastrup <dak@gnu.org> writes: > To be fair: this is what we currently do, and we only actually call > those lambdas that end up actually being recognized by the grammar. > So as long as (primitive-eval `(lambda () ,(read))) is guaranteed to > not ever choke, the potential for error is limited. Come to think of it: an actual optimizing compiler is quite more likely to get annoyed at (lambda () total-garbage-sexp) before one actually tries calling it. So even when letting Guilev1 and Guilev2 compete by letting both use the lambda-based implementation, we might run into more problems using Guilev2 because of having to wrap all _potential_ runtime candidates for lexical evaluation into closures. -- David Kastrup ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-14 17:26 ` Mark H Weaver 2011-12-14 18:23 ` David Kastrup 2011-12-14 18:38 ` Mark H Weaver @ 2011-12-14 22:56 ` Andy Wingo 2 siblings, 0 replies; 82+ messages in thread From: Andy Wingo @ 2011-12-14 22:56 UTC (permalink / raw) To: Mark H Weaver; +Cc: David Kastrup, guile-devel On Wed 14 Dec 2011 18:26, Mark H Weaver <mhw@netris.org> writes: > Andy Wingo <wingo@pobox.com> writes: >> It is my opinion -- and I could be wrong here, either by >> misunderstanding (again!) the issues, or for whatever reason -- that >> closures are the best solution to the #{#} problem. > > I would love to use closures for this, but they simply are not powerful > enough to implement `local-eval' properly. Agreed, but I think at this point that it's fair to say while that lilypond's use case would be more convenient with local-eval, lambda is certainly powerful enough. I'll send a mail tomorrow specifically about local-eval. Andy -- http://wingolog.org/ ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-14 10:08 ` Anything better for delayed lexical evaluation than (lambda () ...)? Andy Wingo 2011-12-14 10:27 ` David Kastrup @ 2011-12-14 11:03 ` Mark H Weaver 2011-12-14 11:18 ` David Kastrup 2011-12-14 13:31 ` Noah Lavine 1 sibling, 2 replies; 82+ messages in thread From: Mark H Weaver @ 2011-12-14 11:03 UTC (permalink / raw) To: Andy Wingo; +Cc: David Kastrup, guile-devel Andy Wingo <wingo@pobox.com> writes: > On Wed 14 Dec 2011 08:50, Mark H Weaver <mhw@netris.org> writes: > >> I have successfully implemented the (capture-lexical-environment) >> special form in the evaluator, and also primitive-local-eval. > > I dunno, Mark. That's a neat hack, but why should we have it in Guile? > It can't compile. It's not for efficiency, because if you wanted more > efficiency, you would compile. So what is it for? It has a cost, so it > needs to justify itself. For starters, it's for handling languages like Lilypond, which fundamentally _cannot_ be compiled. It's not a question of implementation strategy. Even if someone were willing to rewrite Lilypond's implementation from scratch, I have become convinced that it cannot be compiled without redesigning the language itself. More generally, it's to increase the generality and flexibility of Guile. One of the things I like most about Scheme is that it is powerful enough to implement just about anything on top of it, using just about any strategy you wish, in a simple, straightforward manner, thanks to advanced features like macros and continuations. I found it somewhat embarrassing that Guile 2.0 was unable to gracefully support a language that Guile 1.8 handled beautifully. I would like to fix that. Also, I think that it is crucially important to keep the Lilypond developers happy with Guile. We don't have very many users. We should make an effort to keep our existing users happy. Mark ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-14 11:03 ` Mark H Weaver @ 2011-12-14 11:18 ` David Kastrup 2011-12-14 13:31 ` Noah Lavine 1 sibling, 0 replies; 82+ messages in thread From: David Kastrup @ 2011-12-14 11:18 UTC (permalink / raw) To: Mark H Weaver; +Cc: Andy Wingo, guile-devel Mark H Weaver <mhw@netris.org> writes: > Also, I think that it is crucially important to keep the Lilypond > developers happy with Guile. We don't have very many users. We > should make an effort to keep our existing users happy. Basically, you want to be able to write the following in the FAQ: Q: Why should I be picking GUILE instead of other Scheme systems for extending my application that is supposed to expose a user-friendly language? A: Because no other Scheme system, in fact no other system, offers the tools for integrating your language so tightly with Scheme that you can harness its power without having to forego the advantages and features of your own language design. GUILE supports implementations of Emacs Lisp, Lua (and also LilyPond) that rely on advertised interfaces rather than on special-tailored code meddling with GUILE's internals, and that make it easy to access the power of Scheme transparently without losing expressivity in the main user language of your system. -- David Kastrup ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-14 11:03 ` Mark H Weaver 2011-12-14 11:18 ` David Kastrup @ 2011-12-14 13:31 ` Noah Lavine 2011-12-14 21:03 ` Mark H Weaver 1 sibling, 1 reply; 82+ messages in thread From: Noah Lavine @ 2011-12-14 13:31 UTC (permalink / raw) To: Mark H Weaver; +Cc: Andy Wingo, David Kastrup, guile-devel Perhaps this is obvious to everyone else, but it just occurred to me that (capture-local-environment) is just (call-with-current-continuation), but running in the environment of the evaluator instead of the program being evaluated. It's as though the evaluator was going to look in a tree for more code, but hit a special node and did a (call/cc). I hope other people find this interesting. Good morning, Noah ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-14 13:31 ` Noah Lavine @ 2011-12-14 21:03 ` Mark H Weaver 2011-12-14 22:12 ` David Kastrup 2011-12-14 22:55 ` Andy Wingo 0 siblings, 2 replies; 82+ messages in thread From: Mark H Weaver @ 2011-12-14 21:03 UTC (permalink / raw) To: Noah Lavine; +Cc: Andy Wingo, David Kastrup, guile-devel Hi Noah, Noah Lavine <noah.b.lavine@gmail.com> writes: > Perhaps this is obvious to everyone else, but it just occurred to me > that (capture-local-environment) is just > (call-with-current-continuation), but running in the environment of > the evaluator instead of the program being evaluated. It's as though > the evaluator was going to look in a tree for more code, but hit a > special node and did a (call/cc). I hope other people find this > interesting. Ah yes, that's an excellent point! In fact it makes me wonder whether `the-environment' and `local-eval' could actually be implemented this way. I see some complications that might make this strategy impractical or fragile, most notably that we must be assured that the (call/cc) does not happen until (the-environment) would have been _evaluated_, whereas the expander/memoizer/evaluator will want to see what code is there _before_ evaluation. I'll have to think about this. There might be an easy and robust way to do this, or maybe not. Regardless, this is a great way to _think_ about these primitives in terms that Schemers can understand, and maybe a selling point too. I hope we can agree that first-class continuations are insanely great. They carry a significant maintenance cost, but they also add great power when they are needed. So why not first-class continuations for the evaluator itself? :) Thanks! Mark ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-14 21:03 ` Mark H Weaver @ 2011-12-14 22:12 ` David Kastrup 2011-12-14 22:24 ` David Kastrup 2011-12-14 22:55 ` Andy Wingo 1 sibling, 1 reply; 82+ messages in thread From: David Kastrup @ 2011-12-14 22:12 UTC (permalink / raw) To: guile-devel Mark H Weaver <mhw@netris.org> writes: > Hi Noah, > > Noah Lavine <noah.b.lavine@gmail.com> writes: >> Perhaps this is obvious to everyone else, but it just occurred to me >> that (capture-local-environment) is just >> (call-with-current-continuation), but running in the environment of >> the evaluator instead of the program being evaluated. It's as though >> the evaluator was going to look in a tree for more code, but hit a >> special node and did a (call/cc). I hope other people find this >> interesting. > > Ah yes, that's an excellent point! (define (my-eval form env) (call-with-current-continuation (lambda (x) (env (list x form))))) (define-macro (my-env) (call-with-current-continuation identity)) (format #t "~a" (my-eval '(+ x 3) (let ((x 4)) (my-env)))) > In fact it makes me wonder whether `the-environment' and `local-eval' > could actually be implemented this way. I see some complications that > might make this strategy impractical or fragile, most notably that we > must be assured that the (call/cc) does not happen until > (the-environment) would have been _evaluated_, whereas the > expander/memoizer/evaluator will want to see what code is there _before_ > evaluation. I'll have to think about this. There might be an easy and > robust way to do this, or maybe not. Feel free to experiment with the above. I have my doubt that it leads to sane behavior. In particular, it will refinish macro expansion (so you don't want significant material behind it) and reevaluate the _whole_ eval it is in up to the point of calling my-env (so you don't want significant material before it). So it is more a joke than anything of practical value. But is _is_ good for a few dropjaws. -- David Kastrup ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-14 22:12 ` David Kastrup @ 2011-12-14 22:24 ` David Kastrup 0 siblings, 0 replies; 82+ messages in thread From: David Kastrup @ 2011-12-14 22:24 UTC (permalink / raw) To: guile-devel David Kastrup <dak@gnu.org> writes: > (define (my-eval form env) > (call-with-current-continuation > (lambda (x) > (env (list x form))))) > > (define-macro (my-env) > (call-with-current-continuation > identity)) > > > (format #t "~a" (my-eval '(+ x 3) (let ((x 4)) (my-env)))) > > Mark H Weaver <mhw@netris.org> writes: > >> In fact it makes me wonder whether `the-environment' and `local-eval' >> could actually be implemented this way. I see some complications that >> might make this strategy impractical or fragile, most notably that we >> must be assured that the (call/cc) does not happen until >> (the-environment) would have been _evaluated_, whereas the >> expander/memoizer/evaluator will want to see what code is there _before_ >> evaluation. I'll have to think about this. There might be an easy and >> robust way to do this, or maybe not. > > Feel free to experiment with the above. I have my doubt that it leads > to sane behavior. In particular, it will refinish macro expansion (so > you don't want significant material behind it) and reevaluate the > _whole_ eval it is in up to the point of calling my-env (so you don't > want significant material before it). > > So it is more a joke than anything of practical value. But is _is_ good > for a few dropjaws. Actually, if the evaluator does macro expansion on the fly instead of en bloc, it may even be a practical joke. -- David Kastrup ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-14 21:03 ` Mark H Weaver 2011-12-14 22:12 ` David Kastrup @ 2011-12-14 22:55 ` Andy Wingo 1 sibling, 0 replies; 82+ messages in thread From: Andy Wingo @ 2011-12-14 22:55 UTC (permalink / raw) To: Mark H Weaver; +Cc: David Kastrup, guile-devel On Wed 14 Dec 2011 22:03, Mark H Weaver <mhw@netris.org> writes: > Noah Lavine <noah.b.lavine@gmail.com> writes: >> [(the-environment)] is just (call-with-current-continuation), > In fact it makes me wonder whether `the-environment' and `local-eval' > could actually be implemented this way. This prevents the evaluator from running an analysis pass beforehand. It would also prevent syntax expansion from happening beforehand, I think. As an example, Chez Scheme, if it uses the interpreter, actually runs the cp0 pass (like our peval) before passing code off to the interpreter. > Regardless, this is a great way to _think_ about these primitives in > terms that Schemers can understand, and maybe a selling point too. It is an interesting thought experiment, yes ;-) Cheers, Andy -- http://wingolog.org/ ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-13 15:48 ` Andy Wingo 2011-12-13 16:08 ` David Kastrup @ 2011-12-13 16:24 ` David Kastrup 1 sibling, 0 replies; 82+ messages in thread From: David Kastrup @ 2011-12-13 16:24 UTC (permalink / raw) To: Andy Wingo; +Cc: guile-devel Andy Wingo <wingo@pobox.com> writes: > On Tue 13 Dec 2011 16:27, David Kastrup <dak@gnu.org> writes: > >>> It sounds like `current-bindings' is the thing you need. >> >> It will at least be a year before any solution that does not work with >> Guile 1.8 will be accepted into Lilypond. > > It is possible to have similar interfaces with different > implementations, using `cond-expand'. lily.scm does this in one case, > implementing 2.0 interfaces on 1.8. > > I'll take a look at implementing something like this. > > To summarize your issue: you have code like: > > (lambda (a b c) > #{ here I have custom code that references lexical variables; > should it be able to set them too? }#) > > It would be relatively easy to pass in an alist of the lexicals, for > reference purposes; but do you want to be able to set them too, from > within that EDSL? It would appear that program-bindings on an anonymous lambda function that just creates a list of all # and $ scraps in #{ ... #} would deliver that. Then one needs to correlate every structure recursively with the resulting list of bindings, and create an anonymous lambda whenever the intersection is non-empty. It's doable, but its likely easier to just don't bother sorting out the non-environment depending functions from those that do. I should hope that storing and referencing near-trivial lambda functions should not be all too expensive in Guile v2. So without something approaching the comparative seamlessness of the procedure-environment/local-eval pairing, it is not likely that the effort would be warranted. The code currently in Lilypond is working well enough: as I said, I can work with any size of crowbar. And there would be little point to exchange the current hack for a differently tailored and likely more complex hack that is not a part of Guile proper and thus has an even smaller expected live span than the current solution. -- David Kastrup ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-13 15:27 ` David Kastrup 2011-12-13 15:48 ` Andy Wingo @ 2011-12-13 15:52 ` David Kastrup 1 sibling, 0 replies; 82+ messages in thread From: David Kastrup @ 2011-12-13 15:52 UTC (permalink / raw) To: guile-devel David Kastrup <dak@gnu.org> writes: > So I don't think that throwing out _distinguishing_ selling points of > Guile is necessarily doing you a favor. And the transparency with > which it integrates with its language environment and the fact that > one can continue to use its evaluator and debugger even for the > application for which it serves as an extension language, certainly is > a selling point. To illustrate, take a look at <URL:http://nicolas.sceaux.free.fr/prelude/prelude.html>. How likely do you consider an average user to write and master such code? I am currently writing on a report about recent changes in Lilypond, and the code looks like the following (it does not work yet because \parallelMusic is not implemented well enough): ph = #(define-music-function (parser location p1 p2 p3 p4 p5) (ly:pitch? ly:pitch? ly:pitch? ly:pitch? ly:pitch?) #{ r8 $p3 16 $p4 $p5 $p3 $p4 $p5 | r16 $p2 8. ~ $p2 4 | $p1 2 | #}) \parallelMusic #'(high middle low) { \oneVoice | \voiceOne | \voiceTwo | \ph c' e' g' c'' e'' \ph c' d' a' d'' f'' \ph b d' g' d'' f'' \ph c' e' g' c'' e'' \ph c' e' a' e'' a'' \ph c' d' fis' a' d'' [...] \ph d f a c' f' \ph g, d g b f' \oneVoice | \change Staff = "down" \voiceOne | \voiceTwo | \ph c e g c' e' [...] \score { \new PianoStaff << \context Staff = "up" { << \high \\ \middle >> r8 f16 a c' f' c' a c' a f a f d f d r8 g'16 b' d'' f'' d'' b' d'' b' g' b' d' f' e' d' <e' g' c''>1 \bar "|." } \context Staff = "down" { \low << { r16 c8. ~ c4 ~ c2 r16 b,8. ~ b,4 ~ b,2 c1 } \\ { c,2 c, c, c, c,1 } >> } >> \midi { \tempo 4 = 80 } \layout { } } So what do you see: minimal Schemeishness, minimal complexity. That's something a user could actually hope to be writing and understanding. And this "minimal Schemeishness" in spite of the music function \ph being actually written in Scheme is exactly the result of making it seamless and natural to pass in and out of Guile (including taking the lexical environment or a simulation of it along) to the user. If the user wanted to primordinarily deal with cryptic languages and their own data structures and control mechanisms instead of something approximating music notation, she would be using MusiXTeX. You may consider this sort of seamless language integration unimportant because apparently no project but Lilypond has managed approximating it given the documentation of local-eval (and now substituting an emulation of it), but at least Lilypond is not yet in the list of applications trying to get away from Guile for the sake of its users. -- David Kastrup ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-12 21:50 ` Andy Wingo 2011-12-13 9:02 ` David Kastrup @ 2011-12-13 11:14 ` David Kastrup 2011-12-14 13:52 ` Ludovic Courtès 2 siblings, 0 replies; 82+ messages in thread From: David Kastrup @ 2011-12-13 11:14 UTC (permalink / raw) To: guile-devel Andy Wingo <wingo@pobox.com> writes: > Did you see my implementation of `local-eval' [1]? It leverages (hah!) > Guile's macro expander, but otherwise is a straightforward interpreter. > If you find it slow, there are some simple, classic optimizations that > can be made. With some work, it could present a similar interface to > 1.8's `local-eval', `procedure-environment', `the-environment', and such > things. > > [1] http://lists.gnu.org/archive/html/guile-user/2011-02/msg00032.html If you take that message in context, you'll find that a professor of computer science is here asking how one would implement a meta-circular evaluator <URL:http://en.wikipedia.org/wiki/Meta-circular_evaluator> for Guile after local-eval has been thrown away. You answer by giving an example of how to use Guile as the implementation language for a non-meta non-circular evaluator instead. You could likely also use Guile for _bootstrapping_ a meta circular evaluator. But that is not really the same thing. -- David Kastrup ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-12 21:50 ` Andy Wingo 2011-12-13 9:02 ` David Kastrup 2011-12-13 11:14 ` David Kastrup @ 2011-12-14 13:52 ` Ludovic Courtès 2011-12-14 14:27 ` David Kastrup 2 siblings, 1 reply; 82+ messages in thread From: Ludovic Courtès @ 2011-12-14 13:52 UTC (permalink / raw) To: guile-devel Hello! Andy Wingo <wingo@pobox.com> skribis: > But, in the event that David wants to continue with his current > strategy, there are other things that can be done. David, did you know > that Guile's evaluator is implemented in Scheme? That means that if you > want an evaluator with different semantics -- for example, something > closer to Kernel[0], as David appears to want -- then you can implement > an evaluator that provides for fexprs and the like, and it will run > about as well as Guile's evaluator. Indeed. FWIW, Skribilo [0] has its own input language, which is similar to but different from Scheme, so it has its own reader and its own evaluator, the latter being mostly a wrapper around ‘eval’. This strategy has worked well, and portably between 1.8 and 2.0. Thanks, Ludo’. [0] http://nongnu.org/skribilo/ ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-14 13:52 ` Ludovic Courtès @ 2011-12-14 14:27 ` David Kastrup 2011-12-14 21:30 ` Ludovic Courtès 0 siblings, 1 reply; 82+ messages in thread From: David Kastrup @ 2011-12-14 14:27 UTC (permalink / raw) To: guile-devel ludo@gnu.org (Ludovic Courtès) writes: > Andy Wingo <wingo@pobox.com> skribis: > >> But, in the event that David wants to continue with his current >> strategy, there are other things that can be done. David, did you know >> that Guile's evaluator is implemented in Scheme? That means that if you >> want an evaluator with different semantics -- for example, something >> closer to Kernel[0], as David appears to want -- then you can implement >> an evaluator that provides for fexprs and the like, and it will run >> about as well as Guile's evaluator. > > Indeed. FWIW, Skribilo [0] has its own input language, which is similar > to but different from Scheme, so it has its own reader and its own > evaluator, the latter being mostly a wrapper around ‘eval’. This > strategy has worked well, and portably between 1.8 and 2.0. There is a saying "a real FORTRAN programmer can write FORTRAN programs in any language". But one of the principal attractions of implementing a Scheme-like language in Scheme is that one can _turn_ Scheme into one's target language instead of merely using it as an implementation language. In contrast, you usually don't implement a C-like language by manipulating your C compiler (some template libraries may come close), and you usually do not even want to bootstrap a FORTRAN compiler on FORTRAN. And in particular for something like Emacs Lisp, it would be quite more interesting to turn GUILE into Emacs Lisp rather than implement Elisp on top of it. I have not actually looked at the respective code bases. That's just a gut feeling of what would be cool, and also provide a user experience that is not purely either/or regarding what layer one is working in. -- David Kastrup ^ permalink raw reply [flat|nested] 82+ messages in thread
* Re: Anything better for delayed lexical evaluation than (lambda () ...)? 2011-12-14 14:27 ` David Kastrup @ 2011-12-14 21:30 ` Ludovic Courtès 0 siblings, 0 replies; 82+ messages in thread From: Ludovic Courtès @ 2011-12-14 21:30 UTC (permalink / raw) To: guile-devel David Kastrup <dak@gnu.org> skribis: > ludo@gnu.org (Ludovic Courtès) writes: > >> Andy Wingo <wingo@pobox.com> skribis: >> >>> But, in the event that David wants to continue with his current >>> strategy, there are other things that can be done. David, did you know >>> that Guile's evaluator is implemented in Scheme? That means that if you >>> want an evaluator with different semantics -- for example, something >>> closer to Kernel[0], as David appears to want -- then you can implement >>> an evaluator that provides for fexprs and the like, and it will run >>> about as well as Guile's evaluator. >> >> Indeed. FWIW, Skribilo [0] has its own input language, which is similar >> to but different from Scheme, so it has its own reader and its own >> evaluator, the latter being mostly a wrapper around ‘eval’. This >> strategy has worked well, and portably between 1.8 and 2.0. > > There is a saying [...] I just meant to say that this strategy can work, but of course YMMV. Ludo’. ^ permalink raw reply [flat|nested] 82+ messages in thread
end of thread, other threads:[~2012-01-07 17:30 UTC | newest] Thread overview: 82+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2011-12-03 15:45 Anything better for delayed lexical evaluation than (lambda () ...)? David Kastrup 2011-12-03 16:44 ` Andy Wingo 2011-12-06 14:55 ` Thien-Thi Nguyen 2011-12-06 15:45 ` David Kastrup 2011-12-06 19:50 ` Marco Maggi 2011-12-11 9:33 ` David Kastrup 2011-12-11 9:51 ` David Kastrup 2011-12-12 5:21 ` Mark H Weaver 2011-12-12 6:47 ` David Kastrup 2011-12-12 18:29 ` Mark H Weaver 2011-12-12 19:56 ` David Kastrup 2011-12-12 20:39 ` rixed 2011-12-12 21:02 ` David Kastrup 2011-12-12 21:58 ` Mark H Weaver 2011-12-12 21:40 ` Mark H Weaver 2011-12-12 21:50 ` Andy Wingo 2011-12-13 9:02 ` David Kastrup 2011-12-13 13:05 ` Andy Wingo 2011-12-13 13:56 ` David Kastrup 2011-12-13 14:34 ` Andy Wingo 2011-12-13 15:27 ` David Kastrup 2011-12-13 15:48 ` Andy Wingo 2011-12-13 16:08 ` David Kastrup 2011-12-13 16:27 ` Andy Wingo 2011-12-13 16:54 ` David Kastrup 2011-12-13 18:58 ` Andy Wingo 2011-12-13 22:23 ` David Kastrup 2011-12-13 17:28 ` Mark H Weaver 2011-12-13 18:49 ` Andy Wingo 2011-12-13 19:15 ` Mark H Weaver 2011-12-13 23:00 ` Noah Lavine 2011-12-13 23:16 ` David Kastrup 2011-12-13 23:44 ` Andy Wingo 2011-12-13 23:39 ` Andy Wingo 2011-12-13 23:45 ` David Kastrup 2011-12-14 10:15 ` Andy Wingo 2011-12-14 10:32 ` David Kastrup 2011-12-14 0:30 ` Mark H Weaver 2011-12-14 8:16 ` David Kastrup 2011-12-14 0:42 ` Noah Lavine 2011-12-14 0:47 ` Noah Lavine 2011-12-14 1:30 ` Mark H Weaver 2011-12-14 7:50 ` Mark H Weaver 2011-12-14 8:48 ` [PATCH] Implement `capture-lexical-environment' in evaluator Mark H Weaver 2011-12-14 9:08 ` David Kastrup 2011-12-14 9:36 ` Mark H Weaver 2011-12-16 9:21 ` [PATCH] Implement `the-environment' and `local-eval' " Mark H Weaver 2011-12-16 9:32 ` David Kastrup 2011-12-16 14:00 ` Peter TB Brett 2011-12-16 14:26 ` David Kastrup 2011-12-16 15:27 ` Mark H Weaver 2011-12-16 16:01 ` Andy Wingo 2011-12-16 17:44 ` Mark H Weaver 2011-12-16 19:12 ` Mark H Weaver 2012-01-07 1:26 ` Andy Wingo 2012-01-07 17:30 ` Mark H Weaver 2012-01-07 1:18 ` Andy Wingo 2011-12-16 16:59 ` Hans Aberg 2011-12-14 10:08 ` Anything better for delayed lexical evaluation than (lambda () ...)? Andy Wingo 2011-12-14 10:27 ` David Kastrup 2011-12-14 13:35 ` Andy Wingo 2011-12-14 15:21 ` David Kastrup 2011-12-14 15:55 ` Andy Wingo 2011-12-14 17:26 ` Mark H Weaver 2011-12-14 18:23 ` David Kastrup 2011-12-14 18:38 ` Mark H Weaver 2011-12-14 19:14 ` David Kastrup 2011-12-14 19:44 ` David Kastrup 2011-12-14 22:56 ` Andy Wingo 2011-12-14 11:03 ` Mark H Weaver 2011-12-14 11:18 ` David Kastrup 2011-12-14 13:31 ` Noah Lavine 2011-12-14 21:03 ` Mark H Weaver 2011-12-14 22:12 ` David Kastrup 2011-12-14 22:24 ` David Kastrup 2011-12-14 22:55 ` Andy Wingo 2011-12-13 16:24 ` David Kastrup 2011-12-13 15:52 ` David Kastrup 2011-12-13 11:14 ` David Kastrup 2011-12-14 13:52 ` Ludovic Courtès 2011-12-14 14:27 ` David Kastrup 2011-12-14 21:30 ` Ludovic Courtès
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).