I just pushed a patch (authored by Scott) to implement JS's LABEL and BREAK in PS. (Note that this patch deprecates LABELED-FOR since you can get the same effect by combining LABEL and FOR. Was anybody using LABELED-FOR?)
Here's an example:
(label scope (foo) (when (bar) (break scope)) (blee))
=>
scope: { foo(); if (bar()) { break scope; }; blee(); };
I was astonished to discover recently that JS has supported this ability all along in the form of labeled statements and labeled breaks. I'd always assumed that to get explicit returns from an arbitrary scope, you'd have to resort to the ugly hack of muscling TRY/CATCH to do it, thinking that this was the closest JS counterpart. (See http://news.ycombinator.com/item?id=793092 for a thread in which several people believe this.) But it appears we were all wrong.
What's not clear yet is how far this can be taken. Can you use it inside a nested local function to return immediately from the top-level function? That is one thing I've wanted for a long time.
In the ideal case, LABEL/BREAK could be used as a base for implementing a proper BLOCK and RETURN-FROM in PS, something which we'd long believed to be impossible. One challenge is that in CL, RETURN-FROM can take a value, which becomes the value of BLOCK. In other words, BLOCK in CL is an expression while LABEL in JS is not. It seems, though, that most of this challenge has already been conquered with the development of implicit return in PS. The only thing you'd need to add is detecting when BLOCK is being used as an expression, declaring a gensymed variable and assigning whatever is happening inside BLOCK to that variable (much like implicit return already does with e.g. CASE), then put the variable in the expression position that BLOCK was in. It seems like this ought to work. It would also make things like this possible in PS:
(1+ (case foo (:eleven 11) (:twelve 12)))
Vladimir (and everybody), is the above clear? What do you think of it?
Daniel
Makes sense to me. I'll add this to my todo list (which I'll publish in an email as soon as I'm done my current work on the PS compiler).
Vladimir
2010/4/9 Daniel Gackle danielgackle@gmail.com:
I just pushed a patch (authored by Scott) to implement JS's LABEL and BREAK in PS. (Note that this patch deprecates LABELED-FOR since you can get the same effect by combining LABEL and FOR. Was anybody using LABELED-FOR?) Here's an example: (label scope (foo) (when (bar) (break scope)) (blee)) => scope: { foo(); if (bar()) { break scope; }; blee(); }; I was astonished to discover recently that JS has supported this ability all along in the form of labeled statements and labeled breaks. I'd always assumed that to get explicit returns from an arbitrary scope, you'd have to resort to the ugly hack of muscling TRY/CATCH to do it, thinking that this was the closest JS counterpart. (See http://news.ycombinator.com/item?id=793092%C2%A0for a thread in which several people believe this.) But it appears we were all wrong. What's not clear yet is how far this can be taken. Can you use it inside a nested local function to return immediately from the top-level function? That is one thing I've wanted for a long time. In the ideal case, LABEL/BREAK could be used as a base for implementing a proper BLOCK and RETURN-FROM in PS, something which we'd long believed to be impossible. One challenge is that in CL, RETURN-FROM can take a value, which becomes the value of BLOCK. In other words, BLOCK in CL is an expression while LABEL in JS is not. It seems, though, that most of this challenge has already been conquered with the development of implicit return in PS. The only thing you'd need to add is detecting when BLOCK is being used as an expression, declaring a gensymed variable and assigning whatever is happening inside BLOCK to that variable (much like implicit return already does with e.g. CASE), then put the variable in the expression position that BLOCK was in. It seems like this ought to work. It would also make things like this possible in PS: (1+ (case foo (:eleven 11) (:twelve 12))) Vladimir (and everybody), is the above clear? What do you think of it? Daniel _______________________________________________ parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
I added RETURN-FROM and BLOCK without too much effort using the implicit return functionality and try/catch. In my view this is the most reasonable way to implement this in the general case, since BLOCK/RETURN-FROM require non-local exit much in the same way that lisp's TRY/CATCH do.
The alternative to this approach is to exit from each function in the call stack via a Javascript `return' statement. Unfortunately, the call stack can contain many functions code over which the Parenscript compiler exerts little control, requiring throw as the control transfer mechanism. Thus, in the general case of unknown code on the call stack, there is no means to exit without a throw. I do not view throwing as an ugly solution at all, since try/catch was designed for non-local exits of all sorts.
Nonetheless, using try/catch to implement Parenscript features deserves some attention. Programs will need to ensure that they do not use try/catch in a way that interferes with the Parenscript convention. Generally, try/catch blocks should only catch specific exceptions and re-throw PS's exceptions. I'm happy to also implement a safe TRY/CATCH wrapper that re-throws Parenscript errors and catches everything else, too. However, we may want to make an official interface change to try/catch if any lisp-style non-local exit code becomes part of the language.
I present an example of why try/catch is unavoidable inline below:
On Fri, Apr 9, 2010 at 3:42 PM, Vladimir Sedach vsedach@gmail.com wrote:
Makes sense to me. I'll add this to my todo list (which I'll publish in an email as soon as I'm done my current work on the PS compiler).
Vladimir
2010/4/9 Daniel Gackle danielgackle@gmail.com:
I just pushed a patch (authored by Scott) to implement JS's LABEL and BREAK in PS. (Note that this patch deprecates LABELED-FOR since you can get the same effect by combining LABEL and FOR. Was anybody using LABELED-FOR?) Here's an example: (label scope (foo) (when (bar) (break scope)) (blee)) => scope: { foo(); if (bar()) { break scope; }; blee(); }; I was astonished to discover recently that JS has supported this ability all along in the form of labeled statements and labeled breaks. I'd always assumed that to get explicit returns from an arbitrary scope, you'd have to resort to the ugly hack of muscling TRY/CATCH to do it, thinking that this was the closest JS counterpart. (See http://news.ycombinator.com/item?id=793092%C2%A0for a thread in which several people believe this.) But it appears we were all wrong. What's not clear yet is how far this can be taken. Can you use it inside a nested local function to return immediately from the top-level function? That is one thing I've wanted for a long time. In the ideal case, LABEL/BREAK could be used as a base for implementing a proper BLOCK and RETURN-FROM in PS, something which we'd long believed to be impossible. One challenge is that in CL, RETURN-FROM can take a value, which becomes the value of BLOCK. In other words, BLOCK in CL is an expression while LABEL in JS is not. It seems, though, that most of this challenge has already been conquered with the development of implicit return in PS. The only thing you'd need to add is detecting when BLOCK is being used as an expression, declaring a gensymed variable and assigning whatever is happening inside BLOCK to that variable (much like implicit return already does with e.g. CASE), then put the variable in the expression position that BLOCK was in. It seems like this ought to work. It would also make things like this possible in PS: (1+ (case foo (:eleven 11) (:twelve 12))) Vladimir (and everybody), is the above clear? What do you think of it?
As stated above, I think try/catch is the way to go. There is no other way to exit a stack of functions in the general case otherwise.
For example, I can write a Javascript function that calls its argument infinity times and never returns.
function mapForever(fn) { while(true) fn(); }
Now consider some parenscript:
(block non-local (map-forever (lambda () (return-from non-local "we got out!"))))
To extricate itself from map-forever, there is no alternative but JS's throw statement.
Even if we had the ability to alter every function in the system, it would be necessary to inspect nearly every function call's return values to properly unwind the stack to the appropriate BLOCK.
Having said all that, there are cases when try/catch is not necessary for BLOCK/RETURN-FROM, as you have described. BLOCK should emit code according to the contexts in which RETURN-FROM appears. If there is a RETURN-FROM inside the same function, BLOCK can use a label for a local exit. If RETURN-FROM appears inside a lambda, try/catch is necessary (except in cases where you want to optimize this away by inspecting how that lambda gets passed around). If there are no return-froms, just emit a PROGN.
My solution does not do the local optimization, but it does refrain from putting try/catches around code with no return-froms.
Red
Daniel _______________________________________________ parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
Sorry, I forgot the link. See http://github.com/gonzojive/parenscript/commit/d8ebace1bcd60a57470d7a8cf0d4e... for the commit that adds this.
My Parenscript branch is now pretty much in sync with the main branch, with only 2 mostly cosmetic test failures
Red
On Wed, Aug 18, 2010 at 5:12 AM, Red Daly reddaly@gmail.com wrote:
I added RETURN-FROM and BLOCK without too much effort using the implicit return functionality and try/catch. In my view this is the most reasonable way to implement this in the general case, since BLOCK/RETURN-FROM require non-local exit much in the same way that lisp's TRY/CATCH do.
The alternative to this approach is to exit from each function in the call stack via a Javascript `return' statement. Unfortunately, the call stack can contain many functions code over which the Parenscript compiler exerts little control, requiring throw as the control transfer mechanism. Thus, in the general case of unknown code on the call stack, there is no means to exit without a throw. I do not view throwing as an ugly solution at all, since try/catch was designed for non-local exits of all sorts.
Nonetheless, using try/catch to implement Parenscript features deserves some attention. Programs will need to ensure that they do not use try/catch in a way that interferes with the Parenscript convention. Generally, try/catch blocks should only catch specific exceptions and re-throw PS's exceptions. I'm happy to also implement a safe TRY/CATCH wrapper that re-throws Parenscript errors and catches everything else, too. However, we may want to make an official interface change to try/catch if any lisp-style non-local exit code becomes part of the language.
I present an example of why try/catch is unavoidable inline below:
On Fri, Apr 9, 2010 at 3:42 PM, Vladimir Sedach vsedach@gmail.com wrote:
Makes sense to me. I'll add this to my todo list (which I'll publish in an email as soon as I'm done my current work on the PS compiler).
Vladimir
2010/4/9 Daniel Gackle danielgackle@gmail.com:
I just pushed a patch (authored by Scott) to implement JS's LABEL and BREAK in PS. (Note that this patch deprecates LABELED-FOR since you can get the same effect by combining LABEL and FOR. Was anybody using LABELED-FOR?) Here's an example: (label scope (foo) (when (bar) (break scope)) (blee)) => scope: { foo(); if (bar()) { break scope; }; blee(); }; I was astonished to discover recently that JS has supported this ability all along in the form of labeled statements and labeled breaks. I'd always assumed that to get explicit returns from an arbitrary scope, you'd have to resort to the ugly hack of muscling TRY/CATCH to do it, thinking that this was the closest JS counterpart. (See http://news.ycombinator.com/item?id=793092%C2%A0for a thread in which several people believe this.) But it appears we were all wrong. What's not clear yet is how far this can be taken. Can you use it inside a nested local function to return immediately from the top-level function? That is one thing I've wanted for a long time. In the ideal case, LABEL/BREAK could be used as a base for implementing a proper BLOCK and RETURN-FROM in PS, something which we'd long believed to be impossible. One challenge is that in CL, RETURN-FROM can take a value, which becomes the value of BLOCK. In other words, BLOCK in CL is an expression while LABEL in JS is not. It seems, though, that most of this challenge has already been conquered with the development of implicit return in PS. The only thing you'd need to add is detecting when BLOCK is being used as an expression, declaring a gensymed variable and assigning whatever is happening inside BLOCK to that variable (much like implicit return already does with e.g. CASE), then put the variable in the expression position that BLOCK was in. It seems like this ought to work. It would also make things like this possible in PS: (1+ (case foo (:eleven 11) (:twelve 12))) Vladimir (and everybody), is the above clear? What do you think of it?
As stated above, I think try/catch is the way to go. There is no other way to exit a stack of functions in the general case otherwise.
For example, I can write a Javascript function that calls its argument infinity times and never returns.
function mapForever(fn) { while(true) fn(); }
Now consider some parenscript:
(block non-local (map-forever (lambda () (return-from non-local "we got out!"))))
To extricate itself from map-forever, there is no alternative but JS's throw statement.
Even if we had the ability to alter every function in the system, it would be necessary to inspect nearly every function call's return values to properly unwind the stack to the appropriate BLOCK.
Having said all that, there are cases when try/catch is not necessary for BLOCK/RETURN-FROM, as you have described. BLOCK should emit code according to the contexts in which RETURN-FROM appears. If there is a RETURN-FROM inside the same function, BLOCK can use a label for a local exit. If RETURN-FROM appears inside a lambda, try/catch is necessary (except in cases where you want to optimize this away by inspecting how that lambda gets passed around). If there are no return-froms, just emit a PROGN.
My solution does not do the local optimization, but it does refrain from putting try/catches around code with no return-froms.
Red
Daniel _______________________________________________ parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
Awesome!
_Nick_
On Wed, Aug 18, 2010 at 5:14 AM, Red Daly reddaly@gmail.com wrote:
Sorry, I forgot the link. See
http://github.com/gonzojive/parenscript/commit/d8ebace1bcd60a57470d7a8cf0d4e... for the commit that adds this.
My Parenscript branch is now pretty much in sync with the main branch, with only 2 mostly cosmetic test failures
Red
On Wed, Aug 18, 2010 at 5:12 AM, Red Daly reddaly@gmail.com wrote:
I added RETURN-FROM and BLOCK without too much effort using the implicit return functionality and try/catch. In my view this is the most reasonable way to implement this in the general case, since BLOCK/RETURN-FROM require non-local exit much in the same way that lisp's TRY/CATCH do.
The alternative to this approach is to exit from each function in the call stack via a Javascript `return' statement. Unfortunately, the call stack can contain many functions code over which the Parenscript compiler exerts little control, requiring throw as the control transfer mechanism. Thus, in the general case of unknown code on the call stack, there is no means to exit without a throw. I do not view throwing as an ugly solution at all, since try/catch was designed for non-local exits of all sorts.
Nonetheless, using try/catch to implement Parenscript features deserves some attention. Programs will need to ensure that they do not use try/catch in a way that interferes with the Parenscript convention. Generally, try/catch blocks should only catch specific exceptions and re-throw PS's exceptions. I'm happy to also implement a safe TRY/CATCH wrapper that re-throws Parenscript errors and catches everything else, too. However, we may want to make an official interface change to try/catch if any lisp-style non-local exit code becomes part of the language.
I present an example of why try/catch is unavoidable inline below:
On Fri, Apr 9, 2010 at 3:42 PM, Vladimir Sedach vsedach@gmail.com
wrote:
Makes sense to me. I'll add this to my todo list (which I'll publish in an email as soon as I'm done my current work on the PS compiler).
Vladimir
2010/4/9 Daniel Gackle danielgackle@gmail.com:
I just pushed a patch (authored by Scott) to implement JS's LABEL and
BREAK
in PS. (Note that this patch deprecates LABELED-FOR since you can get
the
same effect by combining LABEL and FOR. Was anybody using LABELED-FOR?) Here's an example: (label scope (foo) (when (bar) (break scope)) (blee)) => scope: { foo(); if (bar()) { break scope; }; blee(); }; I was astonished to discover recently that JS has supported this
ability all
along in the form of labeled statements and labeled breaks. I'd always assumed that to get explicit returns from an arbitrary scope, you'd
have to
resort to the ugly hack of muscling TRY/CATCH to do it, thinking that
this
was the closest JS counterpart. (See http://news.ycombinator.com/item?id=793092 for a thread in which several people believe this.) But it appears we were all wrong. What's not clear yet is how far this can be taken. Can you use it
inside a
nested local function to return immediately from the top-level
function?
That is one thing I've wanted for a long time. In the ideal case, LABEL/BREAK could be used as a base for implementing
a
proper BLOCK and RETURN-FROM in PS, something which we'd long believed
to be
impossible. One challenge is that in CL, RETURN-FROM can take a value,
which
becomes the value of BLOCK. In other words, BLOCK in CL is an
expression
while LABEL in JS is not. It seems, though, that most of this challenge
has
already been conquered with the development of implicit return in PS.
The
only thing you'd need to add is detecting when BLOCK is being used as
an
expression, declaring a gensymed variable and assigning whatever is happening inside BLOCK to that variable (much like implicit return
already
does with e.g. CASE), then put the variable in the expression position
that
BLOCK was in. It seems like this ought to work. It would also make
things
like this possible in PS: (1+ (case foo (:eleven 11) (:twelve 12))) Vladimir (and everybody), is the above clear? What do you think of it?
As stated above, I think try/catch is the way to go. There is no other way to exit a stack of functions in the general case otherwise.
For example, I can write a Javascript function that calls its argument infinity times and never returns.
function mapForever(fn) { while(true) fn(); }
Now consider some parenscript:
(block non-local (map-forever (lambda () (return-from non-local "we got out!"))))
To extricate itself from map-forever, there is no alternative but JS's throw statement.
Even if we had the ability to alter every function in the system, it would be necessary to inspect nearly every function call's return values to properly unwind the stack to the appropriate BLOCK.
Having said all that, there are cases when try/catch is not necessary for BLOCK/RETURN-FROM, as you have described. BLOCK should emit code according to the contexts in which RETURN-FROM appears. If there is a RETURN-FROM inside the same function, BLOCK can use a label for a local exit. If RETURN-FROM appears inside a lambda, try/catch is necessary (except in cases where you want to optimize this away by inspecting how that lambda gets passed around). If there are no return-froms, just emit a PROGN.
My solution does not do the local optimization, but it does refrain from putting try/catches around code with no return-froms.
Red
Daniel _______________________________________________ parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
I like your suggestion of emitting TRY/CATCH only in the cases where it's necessary, i.e. only when trying to escape out of more than one level of function nesting, seems like a good way to go. Then you're only paying for the ugliness when you need it. It's in keeping with PS's philosophy of staying close to what one would write by hand.
On Wed, Aug 18, 2010 at 6:12 AM, Red Daly reddaly@gmail.com wrote:
I added RETURN-FROM and BLOCK without too much effort using the implicit return functionality and try/catch. In my view this is the most reasonable way to implement this in the general case, since BLOCK/RETURN-FROM require non-local exit much in the same way that lisp's TRY/CATCH do.
The alternative to this approach is to exit from each function in the call stack via a Javascript `return' statement. Unfortunately, the call stack can contain many functions code over which the Parenscript compiler exerts little control, requiring throw as the control transfer mechanism. Thus, in the general case of unknown code on the call stack, there is no means to exit without a throw. I do not view throwing as an ugly solution at all, since try/catch was designed for non-local exits of all sorts.
Nonetheless, using try/catch to implement Parenscript features deserves some attention. Programs will need to ensure that they do not use try/catch in a way that interferes with the Parenscript convention. Generally, try/catch blocks should only catch specific exceptions and re-throw PS's exceptions. I'm happy to also implement a safe TRY/CATCH wrapper that re-throws Parenscript errors and catches everything else, too. However, we may want to make an official interface change to try/catch if any lisp-style non-local exit code becomes part of the language.
I present an example of why try/catch is unavoidable inline below:
On Fri, Apr 9, 2010 at 3:42 PM, Vladimir Sedach vsedach@gmail.com wrote:
Makes sense to me. I'll add this to my todo list (which I'll publish in an email as soon as I'm done my current work on the PS compiler).
Vladimir
2010/4/9 Daniel Gackle danielgackle@gmail.com:
I just pushed a patch (authored by Scott) to implement JS's LABEL and
BREAK
in PS. (Note that this patch deprecates LABELED-FOR since you can get
the
same effect by combining LABEL and FOR. Was anybody using LABELED-FOR?) Here's an example: (label scope (foo) (when (bar) (break scope)) (blee)) => scope: { foo(); if (bar()) { break scope; }; blee(); }; I was astonished to discover recently that JS has supported this ability
all
along in the form of labeled statements and labeled breaks. I'd always assumed that to get explicit returns from an arbitrary scope, you'd have
to
resort to the ugly hack of muscling TRY/CATCH to do it, thinking that
this
was the closest JS counterpart. (See http://news.ycombinator.com/item?id=793092 for a thread in which several people believe this.) But it appears we were all wrong. What's not clear yet is how far this can be taken. Can you use it inside
a
nested local function to return immediately from the top-level function? That is one thing I've wanted for a long time. In the ideal case, LABEL/BREAK could be used as a base for implementing
a
proper BLOCK and RETURN-FROM in PS, something which we'd long believed
to be
impossible. One challenge is that in CL, RETURN-FROM can take a value,
which
becomes the value of BLOCK. In other words, BLOCK in CL is an expression while LABEL in JS is not. It seems, though, that most of this challenge
has
already been conquered with the development of implicit return in PS.
The
only thing you'd need to add is detecting when BLOCK is being used as an expression, declaring a gensymed variable and assigning whatever is happening inside BLOCK to that variable (much like implicit return
already
does with e.g. CASE), then put the variable in the expression position
that
BLOCK was in. It seems like this ought to work. It would also make
things
like this possible in PS: (1+ (case foo (:eleven 11) (:twelve 12))) Vladimir (and everybody), is the above clear? What do you think of it?
As stated above, I think try/catch is the way to go. There is no other way to exit a stack of functions in the general case otherwise.
For example, I can write a Javascript function that calls its argument infinity times and never returns.
function mapForever(fn) { while(true) fn(); }
Now consider some parenscript:
(block non-local (map-forever (lambda () (return-from non-local "we got out!"))))
To extricate itself from map-forever, there is no alternative but JS's throw statement.
Even if we had the ability to alter every function in the system, it would be necessary to inspect nearly every function call's return values to properly unwind the stack to the appropriate BLOCK.
Having said all that, there are cases when try/catch is not necessary for BLOCK/RETURN-FROM, as you have described. BLOCK should emit code according to the contexts in which RETURN-FROM appears. If there is a RETURN-FROM inside the same function, BLOCK can use a label for a local exit. If RETURN-FROM appears inside a lambda, try/catch is necessary (except in cases where you want to optimize this away by inspecting how that lambda gets passed around). If there are no return-froms, just emit a PROGN.
My solution does not do the local optimization, but it does refrain from putting try/catches around code with no return-froms.
Red
Daniel _______________________________________________ parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
I just pushed a patch that tries to do the right thing with both lexical and dynamic-extent BLOCK (including implicit BLOCK forms) and RETURN-FROM. It's also supposed to provide backwards-compatibility with the old-style RETURN behavior (although that does issue a warning).
The big thing is that right now in most of the interesting cases it does the control jump, but does not return a value. That will be fixed in future patches.
I haven't really tested it, so try it out and let me know what breaks.
Vladimir
2010/8/18 Daniel Gackle danielgackle@gmail.com:
I like your suggestion of emitting TRY/CATCH only in the cases where it's necessary, i.e. only when trying to escape out of more than one level of function nesting, seems like a good way to go. Then you're only paying for the ugliness when you need it. It's in keeping with PS's philosophy of staying close to what one would write by hand.
On Wed, Aug 18, 2010 at 6:12 AM, Red Daly reddaly@gmail.com wrote:
I added RETURN-FROM and BLOCK without too much effort using the implicit return functionality and try/catch. In my view this is the most reasonable way to implement this in the general case, since BLOCK/RETURN-FROM require non-local exit much in the same way that lisp's TRY/CATCH do.
The alternative to this approach is to exit from each function in the call stack via a Javascript `return' statement. Unfortunately, the call stack can contain many functions code over which the Parenscript compiler exerts little control, requiring throw as the control transfer mechanism. Thus, in the general case of unknown code on the call stack, there is no means to exit without a throw. I do not view throwing as an ugly solution at all, since try/catch was designed for non-local exits of all sorts.
Nonetheless, using try/catch to implement Parenscript features deserves some attention. Programs will need to ensure that they do not use try/catch in a way that interferes with the Parenscript convention. Generally, try/catch blocks should only catch specific exceptions and re-throw PS's exceptions. I'm happy to also implement a safe TRY/CATCH wrapper that re-throws Parenscript errors and catches everything else, too. However, we may want to make an official interface change to try/catch if any lisp-style non-local exit code becomes part of the language.
I present an example of why try/catch is unavoidable inline below:
On Fri, Apr 9, 2010 at 3:42 PM, Vladimir Sedach vsedach@gmail.com wrote:
Makes sense to me. I'll add this to my todo list (which I'll publish in an email as soon as I'm done my current work on the PS compiler).
Vladimir
2010/4/9 Daniel Gackle danielgackle@gmail.com:
I just pushed a patch (authored by Scott) to implement JS's LABEL and BREAK in PS. (Note that this patch deprecates LABELED-FOR since you can get the same effect by combining LABEL and FOR. Was anybody using LABELED-FOR?) Here's an example: (label scope (foo) (when (bar) (break scope)) (blee)) => scope: { foo(); if (bar()) { break scope; }; blee(); }; I was astonished to discover recently that JS has supported this ability all along in the form of labeled statements and labeled breaks. I'd always assumed that to get explicit returns from an arbitrary scope, you'd have to resort to the ugly hack of muscling TRY/CATCH to do it, thinking that this was the closest JS counterpart. (See http://news.ycombinator.com/item?id=793092%C2%A0for a thread in which several people believe this.) But it appears we were all wrong. What's not clear yet is how far this can be taken. Can you use it inside a nested local function to return immediately from the top-level function? That is one thing I've wanted for a long time. In the ideal case, LABEL/BREAK could be used as a base for implementing a proper BLOCK and RETURN-FROM in PS, something which we'd long believed to be impossible. One challenge is that in CL, RETURN-FROM can take a value, which becomes the value of BLOCK. In other words, BLOCK in CL is an expression while LABEL in JS is not. It seems, though, that most of this challenge has already been conquered with the development of implicit return in PS. The only thing you'd need to add is detecting when BLOCK is being used as an expression, declaring a gensymed variable and assigning whatever is happening inside BLOCK to that variable (much like implicit return already does with e.g. CASE), then put the variable in the expression position that BLOCK was in. It seems like this ought to work. It would also make things like this possible in PS: (1+ (case foo (:eleven 11) (:twelve 12))) Vladimir (and everybody), is the above clear? What do you think of it?
As stated above, I think try/catch is the way to go. There is no other way to exit a stack of functions in the general case otherwise.
For example, I can write a Javascript function that calls its argument infinity times and never returns.
function mapForever(fn) { while(true) fn(); }
Now consider some parenscript:
(block non-local (map-forever (lambda () (return-from non-local "we got out!"))))
To extricate itself from map-forever, there is no alternative but JS's throw statement.
Even if we had the ability to alter every function in the system, it would be necessary to inspect nearly every function call's return values to properly unwind the stack to the appropriate BLOCK.
Having said all that, there are cases when try/catch is not necessary for BLOCK/RETURN-FROM, as you have described. BLOCK should emit code according to the contexts in which RETURN-FROM appears. If there is a RETURN-FROM inside the same function, BLOCK can use a label for a local exit. If RETURN-FROM appears inside a lambda, try/catch is necessary (except in cases where you want to optimize this away by inspecting how that lambda gets passed around). If there are no return-froms, just emit a PROGN.
My solution does not do the local optimization, but it does refrain from putting try/catches around code with no return-froms.
Red
Daniel _______________________________________________ parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
Sorry for being lazy, but can you post an example or two? This is a feature I will definitely try out. One of the unwanted weaknesses of my code on the JS side is the inability to get out of a top level function from inside a lambda.
On Sat, Nov 13, 2010 at 2:11 PM, Vladimir Sedach vsedach@gmail.com wrote:
I just pushed a patch that tries to do the right thing with both lexical and dynamic-extent BLOCK (including implicit BLOCK forms) and RETURN-FROM. It's also supposed to provide backwards-compatibility with the old-style RETURN behavior (although that does issue a warning).
The big thing is that right now in most of the interesting cases it does the control jump, but does not return a value. That will be fixed in future patches.
I haven't really tested it, so try it out and let me know what breaks.
Vladimir
2010/8/18 Daniel Gackle danielgackle@gmail.com:
I like your suggestion of emitting TRY/CATCH only in the cases where it's necessary, i.e. only when trying to escape out of more than one level of function nesting, seems like a good way to go. Then you're only paying for the ugliness when you need it. It's in keeping with PS's philosophy of staying close to what one would write by hand.
On Wed, Aug 18, 2010 at 6:12 AM, Red Daly reddaly@gmail.com wrote:
I added RETURN-FROM and BLOCK without too much effort using the implicit return functionality and try/catch. In my view this is the most reasonable way to implement this in the general case, since BLOCK/RETURN-FROM require non-local exit much in the same way that lisp's TRY/CATCH do.
The alternative to this approach is to exit from each function in the call stack via a Javascript `return' statement. Unfortunately, the call stack can contain many functions code over which the Parenscript compiler exerts little control, requiring throw as the control transfer mechanism. Thus, in the general case of unknown code on the call stack, there is no means to exit without a throw. I do not view throwing as an ugly solution at all, since try/catch was designed for non-local exits of all sorts.
Nonetheless, using try/catch to implement Parenscript features deserves some attention. Programs will need to ensure that they do not use try/catch in a way that interferes with the Parenscript convention. Generally, try/catch blocks should only catch specific exceptions and re-throw PS's exceptions. I'm happy to also implement a safe TRY/CATCH wrapper that re-throws Parenscript errors and catches everything else, too. However, we may want to make an official interface change to try/catch if any lisp-style non-local exit code becomes part of the language.
I present an example of why try/catch is unavoidable inline below:
On Fri, Apr 9, 2010 at 3:42 PM, Vladimir Sedach vsedach@gmail.com
wrote:
Makes sense to me. I'll add this to my todo list (which I'll publish in an email as soon as I'm done my current work on the PS compiler).
Vladimir
2010/4/9 Daniel Gackle danielgackle@gmail.com:
I just pushed a patch (authored by Scott) to implement JS's LABEL and BREAK in PS. (Note that this patch deprecates LABELED-FOR since you can get the same effect by combining LABEL and FOR. Was anybody using
LABELED-FOR?)
Here's an example: (label scope (foo) (when (bar) (break scope)) (blee)) => scope: { foo(); if (bar()) { break scope; }; blee(); }; I was astonished to discover recently that JS has supported this ability all along in the form of labeled statements and labeled breaks. I'd
always
assumed that to get explicit returns from an arbitrary scope, you'd have to resort to the ugly hack of muscling TRY/CATCH to do it, thinking that this was the closest JS counterpart. (See http://news.ycombinator.com/item?id=793092 for a thread in
which
several people believe this.) But it appears we were all wrong. What's not clear yet is how far this can be taken. Can you use it inside a nested local function to return immediately from the top-level function? That is one thing I've wanted for a long time. In the ideal case, LABEL/BREAK could be used as a base for
implementing
a proper BLOCK and RETURN-FROM in PS, something which we'd long
believed
to be impossible. One challenge is that in CL, RETURN-FROM can take a
value,
which becomes the value of BLOCK. In other words, BLOCK in CL is an expression while LABEL in JS is not. It seems, though, that most of this
challenge
has already been conquered with the development of implicit return in PS. The only thing you'd need to add is detecting when BLOCK is being used as an expression, declaring a gensymed variable and assigning whatever is happening inside BLOCK to that variable (much like implicit return already does with e.g. CASE), then put the variable in the expression
position
that BLOCK was in. It seems like this ought to work. It would also make things like this possible in PS: (1+ (case foo (:eleven 11) (:twelve 12))) Vladimir (and everybody), is the above clear? What do you think of
it?
As stated above, I think try/catch is the way to go. There is no other way to exit a stack of functions in the general case otherwise.
For example, I can write a Javascript function that calls its argument infinity times and never returns.
function mapForever(fn) { while(true) fn(); }
Now consider some parenscript:
(block non-local (map-forever (lambda () (return-from non-local "we got out!"))))
To extricate itself from map-forever, there is no alternative but JS's throw statement.
Even if we had the ability to alter every function in the system, it would be necessary to inspect nearly every function call's return values to properly unwind the stack to the appropriate BLOCK.
Having said all that, there are cases when try/catch is not necessary for BLOCK/RETURN-FROM, as you have described. BLOCK should emit code according to the contexts in which RETURN-FROM appears. If there is a RETURN-FROM inside the same function, BLOCK can use a label for a local exit. If RETURN-FROM appears inside a lambda, try/catch is necessary (except in cases where you want to optimize this away by inspecting how that lambda gets passed around). If there are no return-froms, just emit a PROGN.
My solution does not do the local optimization, but it does refrain from putting try/catches around code with no return-froms.
Red
Daniel _______________________________________________ parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
Dammit, I was counting on being the lazy one.
There's 6 different situations that block and return-from can be involved in:
1. implicit nil block in iteration forms (do/dolist etc.) in lexical extent:
(ps (dolist (x '(1 2 3)) (when (= x 1) (return))))
=>
for (var x = null, _js_arrvar2 = [1, 2, 3], _js_idx1 = 0; _js_idx1 < _js_arrvar2.length; _js_idx1 += 1) { x = _js_arrvar2[_js_idx1]; if (x === 1) { break; }; };
2. explicit nil or named block in lexical extent (we have to assign a name to the nil block):
(ps (block nil (return) (+ 1 2)))
=>
nilblock: { break nilblock; 1 + 2; };
3. implicit named block established by defun, flet, and labels with lexical extent:
(defun foo () (return-from foo))
=>
function foo() { return null; };
4. implicit named block established by defun, flet, and labels with dynamic extent:
(defun foo () ((lambda () (return-from foo))))
=>
function foo() { try { return (function () { throw { 'ps-block-tag' : 'foo', 'ps-return-value' : null }; })(); } catch (err) { if (err && 'foo' === err['ps-block-tag']) { err['ps-return-value']; } else { throw err; }; }; };
5. explicit named block with dynamic extent:
(block nil ((lambda () (return))) (+ 1 2))
=>
nilblock: { try { (function () { throw { 'ps-block-tag' : 'nilblock', 'ps-return-value' : null }; })(); 1 + 2; } catch (err) { if (err && 'nilblock' === err['ps-block-tag']) { err['ps-return-value']; } else { throw err; }; }; };
6. implicit nil block in iteration forms with dynamic extent return
(ps (dolist (x '(1 2 3)) ((lambda (x) (when (= x 1) (return))) x)))
=>
Which is currently not implemented
Vladimir
2010/11/13 Daniel Gackle danielgackle@gmail.com:
Sorry for being lazy, but can you post an example or two? This is a feature I will definitely try out. One of the unwanted weaknesses of my code on the JS side is the inability to get out of a top level function from inside a lambda.
On Sat, Nov 13, 2010 at 2:11 PM, Vladimir Sedach vsedach@gmail.com wrote:
I just pushed a patch that tries to do the right thing with both lexical and dynamic-extent BLOCK (including implicit BLOCK forms) and RETURN-FROM. It's also supposed to provide backwards-compatibility with the old-style RETURN behavior (although that does issue a warning).
The big thing is that right now in most of the interesting cases it does the control jump, but does not return a value. That will be fixed in future patches.
I haven't really tested it, so try it out and let me know what breaks.
Vladimir
2010/8/18 Daniel Gackle danielgackle@gmail.com:
I like your suggestion of emitting TRY/CATCH only in the cases where it's necessary, i.e. only when trying to escape out of more than one level of function nesting, seems like a good way to go. Then you're only paying for the ugliness when you need it. It's in keeping with PS's philosophy of staying close to what one would write by hand.
On Wed, Aug 18, 2010 at 6:12 AM, Red Daly reddaly@gmail.com wrote:
I added RETURN-FROM and BLOCK without too much effort using the implicit return functionality and try/catch. In my view this is the most reasonable way to implement this in the general case, since BLOCK/RETURN-FROM require non-local exit much in the same way that lisp's TRY/CATCH do.
The alternative to this approach is to exit from each function in the call stack via a Javascript `return' statement. Unfortunately, the call stack can contain many functions code over which the Parenscript compiler exerts little control, requiring throw as the control transfer mechanism. Thus, in the general case of unknown code on the call stack, there is no means to exit without a throw. I do not view throwing as an ugly solution at all, since try/catch was designed for non-local exits of all sorts.
Nonetheless, using try/catch to implement Parenscript features deserves some attention. Programs will need to ensure that they do not use try/catch in a way that interferes with the Parenscript convention. Generally, try/catch blocks should only catch specific exceptions and re-throw PS's exceptions. I'm happy to also implement a safe TRY/CATCH wrapper that re-throws Parenscript errors and catches everything else, too. However, we may want to make an official interface change to try/catch if any lisp-style non-local exit code becomes part of the language.
I present an example of why try/catch is unavoidable inline below:
On Fri, Apr 9, 2010 at 3:42 PM, Vladimir Sedach vsedach@gmail.com wrote:
Makes sense to me. I'll add this to my todo list (which I'll publish in an email as soon as I'm done my current work on the PS compiler).
Vladimir
2010/4/9 Daniel Gackle danielgackle@gmail.com:
I just pushed a patch (authored by Scott) to implement JS's LABEL and BREAK in PS. (Note that this patch deprecates LABELED-FOR since you can get the same effect by combining LABEL and FOR. Was anybody using LABELED-FOR?) Here's an example: (label scope (foo) (when (bar) (break scope)) (blee)) => scope: { foo(); if (bar()) { break scope; }; blee(); }; I was astonished to discover recently that JS has supported this ability all along in the form of labeled statements and labeled breaks. I'd always assumed that to get explicit returns from an arbitrary scope, you'd have to resort to the ugly hack of muscling TRY/CATCH to do it, thinking that this was the closest JS counterpart. (See http://news.ycombinator.com/item?id=793092%C2%A0for a thread in which several people believe this.) But it appears we were all wrong. What's not clear yet is how far this can be taken. Can you use it inside a nested local function to return immediately from the top-level function? That is one thing I've wanted for a long time. In the ideal case, LABEL/BREAK could be used as a base for implementing a proper BLOCK and RETURN-FROM in PS, something which we'd long believed to be impossible. One challenge is that in CL, RETURN-FROM can take a value, which becomes the value of BLOCK. In other words, BLOCK in CL is an expression while LABEL in JS is not. It seems, though, that most of this challenge has already been conquered with the development of implicit return in PS. The only thing you'd need to add is detecting when BLOCK is being used as an expression, declaring a gensymed variable and assigning whatever is happening inside BLOCK to that variable (much like implicit return already does with e.g. CASE), then put the variable in the expression position that BLOCK was in. It seems like this ought to work. It would also make things like this possible in PS: (1+ (case foo (:eleven 11) (:twelve 12))) Vladimir (and everybody), is the above clear? What do you think of it?
As stated above, I think try/catch is the way to go. There is no other way to exit a stack of functions in the general case otherwise.
For example, I can write a Javascript function that calls its argument infinity times and never returns.
function mapForever(fn) { while(true) fn(); }
Now consider some parenscript:
(block non-local (map-forever (lambda () (return-from non-local "we got out!"))))
To extricate itself from map-forever, there is no alternative but JS's throw statement.
Even if we had the ability to alter every function in the system, it would be necessary to inspect nearly every function call's return values to properly unwind the stack to the appropriate BLOCK.
Having said all that, there are cases when try/catch is not necessary for BLOCK/RETURN-FROM, as you have described. BLOCK should emit code according to the contexts in which RETURN-FROM appears. If there is a RETURN-FROM inside the same function, BLOCK can use a label for a local exit. If RETURN-FROM appears inside a lambda, try/catch is necessary (except in cases where you want to optimize this away by inspecting how that lambda gets passed around). If there are no return-froms, just emit a PROGN.
My solution does not do the local optimization, but it does refrain from putting try/catches around code with no return-froms.
Red
Daniel _______________________________________________ parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
Did implementing this change with the modifications you made around how expressionize works? My original patch for block/return-from used expressionize, perhaps somewhat extended. I took a peek at the patch but did not review it in detail. Are you satisfied with the current approach to how you worked this in, or did you encounter some idiosyncrasies?
Just trying to keep tabs on how the code evolved and contributors' attitudes towards it
-- Red
On Sun, Nov 14, 2010 at 4:44 PM, Vladimir Sedach vsedach@gmail.com wrote:
Dammit, I was counting on being the lazy one.
There's 6 different situations that block and return-from can be involved in:
- implicit nil block in iteration forms (do/dolist etc.) in lexical
extent:
(ps (dolist (x '(1 2 3)) (when (= x 1) (return))))
=>
for (var x = null, _js_arrvar2 = [1, 2, 3], _js_idx1 = 0; _js_idx1 < _js_arrvar2.length; _js_idx1 += 1) { x = _js_arrvar2[_js_idx1]; if (x === 1) { break; }; };
- explicit nil or named block in lexical extent (we have to assign a
name to the nil block):
(ps (block nil (return) (+ 1 2)))
=>
nilblock: { break nilblock; 1 + 2; };
- implicit named block established by defun, flet, and labels with
lexical extent:
(defun foo () (return-from foo))
=>
function foo() { return null; };
- implicit named block established by defun, flet, and labels with
dynamic extent:
(defun foo () ((lambda () (return-from foo))))
=>
function foo() { try { return (function () { throw { 'ps-block-tag' : 'foo', 'ps-return-value' : null }; })(); } catch (err) { if (err && 'foo' === err['ps-block-tag']) { err['ps-return-value']; } else { throw err; }; }; };
- explicit named block with dynamic extent:
(block nil ((lambda () (return))) (+ 1 2))
=>
nilblock: { try { (function () { throw { 'ps-block-tag' : 'nilblock', 'ps-return-value' : null }; })(); 1 + 2; } catch (err) { if (err && 'nilblock' === err['ps-block-tag']) { err['ps-return-value']; } else { throw err; }; }; };
- implicit nil block in iteration forms with dynamic extent return
(ps (dolist (x '(1 2 3)) ((lambda (x) (when (= x 1) (return))) x)))
=>
Which is currently not implemented
Vladimir
2010/11/13 Daniel Gackle danielgackle@gmail.com:
Sorry for being lazy, but can you post an example or two? This is a
feature
I will definitely try out. One of the unwanted weaknesses of my code on
the
JS side is the inability to get out of a top level function from inside a lambda.
On Sat, Nov 13, 2010 at 2:11 PM, Vladimir Sedach vsedach@gmail.com
wrote:
I just pushed a patch that tries to do the right thing with both lexical and dynamic-extent BLOCK (including implicit BLOCK forms) and RETURN-FROM. It's also supposed to provide backwards-compatibility with the old-style RETURN behavior (although that does issue a warning).
The big thing is that right now in most of the interesting cases it does the control jump, but does not return a value. That will be fixed in future patches.
I haven't really tested it, so try it out and let me know what breaks.
Vladimir
2010/8/18 Daniel Gackle danielgackle@gmail.com:
I like your suggestion of emitting TRY/CATCH only in the cases where it's necessary, i.e. only when trying to escape out of more than one level of function nesting, seems like a good way to go. Then you're only paying for the ugliness when you need it. It's in keeping with PS's philosophy of staying close to what one would write by hand.
On Wed, Aug 18, 2010 at 6:12 AM, Red Daly reddaly@gmail.com wrote:
I added RETURN-FROM and BLOCK without too much effort using the implicit return functionality and try/catch. In my view this is the most reasonable way to implement this in the general case, since BLOCK/RETURN-FROM require non-local exit much in the same way that lisp's TRY/CATCH do.
The alternative to this approach is to exit from each function in the call stack via a Javascript `return' statement. Unfortunately, the call stack can contain many functions code over which the Parenscript compiler exerts little control, requiring throw as the control transfer mechanism. Thus, in the general case of unknown code on the call stack, there is no means to exit without a throw. I do not view throwing as an ugly solution at all, since try/catch was designed for non-local exits of all sorts.
Nonetheless, using try/catch to implement Parenscript features deserves some attention. Programs will need to ensure that they do not use try/catch in a way that interferes with the Parenscript convention. Generally, try/catch blocks should only catch specific exceptions and re-throw PS's exceptions. I'm happy to also
implement
a safe TRY/CATCH wrapper that re-throws Parenscript errors and
catches
everything else, too. However, we may want to make an official interface change to try/catch if any lisp-style non-local exit code becomes part of the language.
I present an example of why try/catch is unavoidable inline below:
On Fri, Apr 9, 2010 at 3:42 PM, Vladimir Sedach vsedach@gmail.com wrote:
Makes sense to me. I'll add this to my todo list (which I'll
publish
in an email as soon as I'm done my current work on the PS
compiler).
Vladimir
2010/4/9 Daniel Gackle danielgackle@gmail.com: > I just pushed a patch (authored by Scott) to implement JS's LABEL > and > BREAK > in PS. (Note that this patch deprecates LABELED-FOR since you can > get > the > same effect by combining LABEL and FOR. Was anybody using > LABELED-FOR?) > Here's an example: > (label scope > (foo) > (when (bar) > (break scope)) > (blee)) > => > scope: { > foo(); > if (bar()) { > break scope; > }; > blee(); > }; > I was astonished to discover recently that JS has supported this > ability all > along in the form of labeled statements and labeled breaks. I'd > always > assumed that to get explicit returns from an arbitrary scope,
you'd
> have to > resort to the ugly hack of muscling TRY/CATCH to do it, thinking > that > this > was the closest JS counterpart. > (See http://news.ycombinator.com/item?id=793092 for a thread in > which > several people believe this.) But it appears we were all wrong. > What's not clear yet is how far this can be taken. Can you use it > inside a > nested local function to return immediately from the top-level > function? > That is one thing I've wanted for a long time. > In the ideal case, LABEL/BREAK could be used as a base for > implementing > a > proper BLOCK and RETURN-FROM in PS, something which we'd long > believed > to be > impossible. One challenge is that in CL, RETURN-FROM can take a > value, > which > becomes the value of BLOCK. In other words, BLOCK in CL is an > expression > while LABEL in JS is not. It seems, though, that most of this > challenge > has > already been conquered with the development of implicit return in > PS. > The > only thing you'd need to add is detecting when BLOCK is being used > as > an > expression, declaring a gensymed variable and assigning whatever
is
> happening inside BLOCK to that variable (much like implicit return > already > does with e.g. CASE), then put the variable in the expression > position > that > BLOCK was in. It seems like this ought to work. It would also make > things > like this possible in PS: > (1+ (case foo > (:eleven 11) > (:twelve 12))) > Vladimir (and everybody), is the above clear? What do you think of > it?
As stated above, I think try/catch is the way to go. There is no other way to exit a stack of functions in the general case otherwise.
For example, I can write a Javascript function that calls its
argument
infinity times and never returns.
function mapForever(fn) { while(true) fn(); }
Now consider some parenscript:
(block non-local (map-forever (lambda () (return-from non-local "we got out!"))))
To extricate itself from map-forever, there is no alternative but
JS's
throw statement.
Even if we had the ability to alter every function in the system, it would be necessary to inspect nearly every function call's return values to properly unwind the stack to the appropriate BLOCK.
Having said all that, there are cases when try/catch is not necessary for BLOCK/RETURN-FROM, as you have described. BLOCK should emit code according to the contexts in which RETURN-FROM appears. If there is
a
RETURN-FROM inside the same function, BLOCK can use a label for a local exit. If RETURN-FROM appears inside a lambda, try/catch is necessary (except in cases where you want to optimize this away by inspecting how that lambda gets passed around). If there are no return-froms, just emit a PROGN.
My solution does not do the local optimization, but it does refrain from putting try/catches around code with no return-froms.
Red
> Daniel > _______________________________________________ > parenscript-devel mailing list > parenscript-devel@common-lisp.net > http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel > >
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
Did implementing this change with the modifications you made around how expressionize works? My original patch for block/return-from used expressionize, perhaps somewhat extended. I took a peek at the patch but did not review it in detail. Are you satisfied with the current approach to how you worked this in, or did you encounter some idiosyncrasies?
Yeah, I basically had to throw out the idea of expressionize. It only really worked with RETURN because it depended on being able to return control from within certain places in a certain way. I tried to extend multiple-value-bind to handle that correctly, then it became apparent that to "expressionize" things the right way means different things for different combinations of special forms. Luckily it's not a full Cartesian product as the special operators can be grouped into a few common classes. But it gets kind of tricky because some combinations need to mix both transferring control and passing values.
Just trying to keep tabs on how the code evolved and contributors' attitudes towards it
Well, I know what the contributors' attitudes towards it are - "you f$%#^* changed everything and broke all of my code."
The major thing I did that will cause merge problems for everyone is split up how special operators are defined: with the new interface, you're encouraged to explicitly specify different expansion code for how the operator is expanded when its parent form wants an expression, and when it wants a statement.
What I want to do is take a look at everyone's Parenscript forks that add new features (so far those would be yours and 3b's, anyone else have a fork?) and look at their new patches and try to get all the interesting stuff into Parenscript mainline.
I also suspect that with a little judicious inlining, some of the current run-time library functions can be implemented in Parenscript itself.
Vladimir
On Sun, Nov 14, 2010 at 4:44 PM, Vladimir Sedach vsedach@gmail.com wrote:
Dammit, I was counting on being the lazy one.
There's 6 different situations that block and return-from can be involved in:
- implicit nil block in iteration forms (do/dolist etc.) in lexical
extent:
(ps (dolist (x '(1 2 3)) (when (= x 1) (return))))
=>
for (var x = null, _js_arrvar2 = [1, 2, 3], _js_idx1 = 0; _js_idx1 < _js_arrvar2.length; _js_idx1 += 1) { x = _js_arrvar2[_js_idx1]; if (x === 1) { break; }; };
- explicit nil or named block in lexical extent (we have to assign a
name to the nil block):
(ps (block nil (return) (+ 1 2)))
=>
nilblock: { break nilblock; 1 + 2; };
- implicit named block established by defun, flet, and labels with
lexical extent:
(defun foo () (return-from foo))
=>
function foo() { return null; };
- implicit named block established by defun, flet, and labels with
dynamic extent:
(defun foo () ((lambda () (return-from foo))))
=>
function foo() { try { return (function () { throw { 'ps-block-tag' : 'foo', 'ps-return-value' : null }; })(); } catch (err) { if (err && 'foo' === err['ps-block-tag']) { err['ps-return-value']; } else { throw err; }; }; };
- explicit named block with dynamic extent:
(block nil ((lambda () (return))) (+ 1 2))
=>
nilblock: { try { (function () { throw { 'ps-block-tag' : 'nilblock', 'ps-return-value' : null }; })(); 1 + 2; } catch (err) { if (err && 'nilblock' === err['ps-block-tag']) { err['ps-return-value']; } else { throw err; }; }; };
- implicit nil block in iteration forms with dynamic extent return
(ps (dolist (x '(1 2 3)) ((lambda (x) (when (= x 1) (return))) x)))
=>
Which is currently not implemented
Vladimir
2010/11/13 Daniel Gackle danielgackle@gmail.com:
Sorry for being lazy, but can you post an example or two? This is a feature I will definitely try out. One of the unwanted weaknesses of my code on the JS side is the inability to get out of a top level function from inside a lambda.
On Sat, Nov 13, 2010 at 2:11 PM, Vladimir Sedach vsedach@gmail.com wrote:
I just pushed a patch that tries to do the right thing with both lexical and dynamic-extent BLOCK (including implicit BLOCK forms) and RETURN-FROM. It's also supposed to provide backwards-compatibility with the old-style RETURN behavior (although that does issue a warning).
The big thing is that right now in most of the interesting cases it does the control jump, but does not return a value. That will be fixed in future patches.
I haven't really tested it, so try it out and let me know what breaks.
Vladimir
2010/8/18 Daniel Gackle danielgackle@gmail.com:
I like your suggestion of emitting TRY/CATCH only in the cases where it's necessary, i.e. only when trying to escape out of more than one level of function nesting, seems like a good way to go. Then you're only paying for the ugliness when you need it. It's in keeping with PS's philosophy of staying close to what one would write by hand.
On Wed, Aug 18, 2010 at 6:12 AM, Red Daly reddaly@gmail.com wrote:
I added RETURN-FROM and BLOCK without too much effort using the implicit return functionality and try/catch. In my view this is the most reasonable way to implement this in the general case, since BLOCK/RETURN-FROM require non-local exit much in the same way that lisp's TRY/CATCH do.
The alternative to this approach is to exit from each function in the call stack via a Javascript `return' statement. Unfortunately, the call stack can contain many functions code over which the Parenscript compiler exerts little control, requiring throw as the control transfer mechanism. Thus, in the general case of unknown code on the call stack, there is no means to exit without a throw. I do not view throwing as an ugly solution at all, since try/catch was designed for non-local exits of all sorts.
Nonetheless, using try/catch to implement Parenscript features deserves some attention. Programs will need to ensure that they do not use try/catch in a way that interferes with the Parenscript convention. Generally, try/catch blocks should only catch specific exceptions and re-throw PS's exceptions. I'm happy to also implement a safe TRY/CATCH wrapper that re-throws Parenscript errors and catches everything else, too. However, we may want to make an official interface change to try/catch if any lisp-style non-local exit code becomes part of the language.
I present an example of why try/catch is unavoidable inline below:
On Fri, Apr 9, 2010 at 3:42 PM, Vladimir Sedach vsedach@gmail.com wrote: > Makes sense to me. I'll add this to my todo list (which I'll > publish > in an email as soon as I'm done my current work on the PS > compiler). > > Vladimir > > 2010/4/9 Daniel Gackle danielgackle@gmail.com: >> I just pushed a patch (authored by Scott) to implement JS's LABEL >> and >> BREAK >> in PS. (Note that this patch deprecates LABELED-FOR since you can >> get >> the >> same effect by combining LABEL and FOR. Was anybody using >> LABELED-FOR?) >> Here's an example: >> (label scope >> (foo) >> (when (bar) >> (break scope)) >> (blee)) >> => >> scope: { >> foo(); >> if (bar()) { >> break scope; >> }; >> blee(); >> }; >> I was astonished to discover recently that JS has supported this >> ability all >> along in the form of labeled statements and labeled breaks. I'd >> always >> assumed that to get explicit returns from an arbitrary scope, >> you'd >> have to >> resort to the ugly hack of muscling TRY/CATCH to do it, thinking >> that >> this >> was the closest JS counterpart. >> (See http://news.ycombinator.com/item?id=793092%C2%A0for a thread in >> which >> several people believe this.) But it appears we were all wrong. >> What's not clear yet is how far this can be taken. Can you use it >> inside a >> nested local function to return immediately from the top-level >> function? >> That is one thing I've wanted for a long time. >> In the ideal case, LABEL/BREAK could be used as a base for >> implementing >> a >> proper BLOCK and RETURN-FROM in PS, something which we'd long >> believed >> to be >> impossible. One challenge is that in CL, RETURN-FROM can take a >> value, >> which >> becomes the value of BLOCK. In other words, BLOCK in CL is an >> expression >> while LABEL in JS is not. It seems, though, that most of this >> challenge >> has >> already been conquered with the development of implicit return in >> PS. >> The >> only thing you'd need to add is detecting when BLOCK is being >> used >> as >> an >> expression, declaring a gensymed variable and assigning whatever >> is >> happening inside BLOCK to that variable (much like implicit >> return >> already >> does with e.g. CASE), then put the variable in the expression >> position >> that >> BLOCK was in. It seems like this ought to work. It would also >> make >> things >> like this possible in PS: >> (1+ (case foo >> (:eleven 11) >> (:twelve 12))) >> Vladimir (and everybody), is the above clear? What do you think >> of >> it?
As stated above, I think try/catch is the way to go. There is no other way to exit a stack of functions in the general case otherwise.
For example, I can write a Javascript function that calls its argument infinity times and never returns.
function mapForever(fn) { while(true) fn(); }
Now consider some parenscript:
(block non-local (map-forever (lambda () (return-from non-local "we got out!"))))
To extricate itself from map-forever, there is no alternative but JS's throw statement.
Even if we had the ability to alter every function in the system, it would be necessary to inspect nearly every function call's return values to properly unwind the stack to the appropriate BLOCK.
Having said all that, there are cases when try/catch is not necessary for BLOCK/RETURN-FROM, as you have described. BLOCK should emit code according to the contexts in which RETURN-FROM appears. If there is a RETURN-FROM inside the same function, BLOCK can use a label for a local exit. If RETURN-FROM appears inside a lambda, try/catch is necessary (except in cases where you want to optimize this away by inspecting how that lambda gets passed around). If there are no return-froms, just emit a PROGN.
My solution does not do the local optimization, but it does refrain from putting try/catches around code with no return-froms.
Red
>> Daniel >> _______________________________________________ >> parenscript-devel mailing list >> parenscript-devel@common-lisp.net >> http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel >> >> > > _______________________________________________ > parenscript-devel mailing list > parenscript-devel@common-lisp.net > http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel >
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
Thank you for these examples, and sorry for the delay in replying. They are invaluable in figuring out what you've done with BLOCK and RETURN-FROM.
In example #5, the following line:
err['ps-return-value'];
fails to actually return anything. Presumably it should be:
return err['ps-return-value'];
Daniel
On Sun, Nov 14, 2010 at 5:44 PM, Vladimir Sedach vsedach@gmail.com wrote:
Dammit, I was counting on being the lazy one.
There's 6 different situations that block and return-from can be involved in:
- implicit nil block in iteration forms (do/dolist etc.) in lexical
extent:
(ps (dolist (x '(1 2 3)) (when (= x 1) (return))))
=>
for (var x = null, _js_arrvar2 = [1, 2, 3], _js_idx1 = 0; _js_idx1 < _js_arrvar2.length; _js_idx1 += 1) { x = _js_arrvar2[_js_idx1]; if (x === 1) { break; }; };
- explicit nil or named block in lexical extent (we have to assign a
name to the nil block):
(ps (block nil (return) (+ 1 2)))
=>
nilblock: { break nilblock; 1 + 2; };
- implicit named block established by defun, flet, and labels with
lexical extent:
(defun foo () (return-from foo))
=>
function foo() { return null; };
- implicit named block established by defun, flet, and labels with
dynamic extent:
(defun foo () ((lambda () (return-from foo))))
=>
function foo() { try { return (function () { throw { 'ps-block-tag' : 'foo', 'ps-return-value' : null }; })(); } catch (err) { if (err && 'foo' === err['ps-block-tag']) { err['ps-return-value']; } else { throw err; }; }; };
- explicit named block with dynamic extent:
(block nil ((lambda () (return))) (+ 1 2))
=>
nilblock: { try { (function () { throw { 'ps-block-tag' : 'nilblock', 'ps-return-value' : null }; })(); 1 + 2; } catch (err) { if (err && 'nilblock' === err['ps-block-tag']) { err['ps-return-value']; } else { throw err; }; }; };
- implicit nil block in iteration forms with dynamic extent return
(ps (dolist (x '(1 2 3)) ((lambda (x) (when (= x 1) (return))) x)))
=>
Which is currently not implemented
Vladimir
2010/11/13 Daniel Gackle danielgackle@gmail.com:
Sorry for being lazy, but can you post an example or two? This is a
feature
I will definitely try out. One of the unwanted weaknesses of my code on
the
JS side is the inability to get out of a top level function from inside a lambda.
On Sat, Nov 13, 2010 at 2:11 PM, Vladimir Sedach vsedach@gmail.com
wrote:
I just pushed a patch that tries to do the right thing with both lexical and dynamic-extent BLOCK (including implicit BLOCK forms) and RETURN-FROM. It's also supposed to provide backwards-compatibility with the old-style RETURN behavior (although that does issue a warning).
The big thing is that right now in most of the interesting cases it does the control jump, but does not return a value. That will be fixed in future patches.
I haven't really tested it, so try it out and let me know what breaks.
Vladimir
2010/8/18 Daniel Gackle danielgackle@gmail.com:
I like your suggestion of emitting TRY/CATCH only in the cases where it's necessary, i.e. only when trying to escape out of more than one level of function nesting, seems like a good way to go. Then you're only paying for the ugliness when you need it. It's in keeping with PS's philosophy of staying close to what one would write by hand.
On Wed, Aug 18, 2010 at 6:12 AM, Red Daly reddaly@gmail.com wrote:
I added RETURN-FROM and BLOCK without too much effort using the implicit return functionality and try/catch. In my view this is the most reasonable way to implement this in the general case, since BLOCK/RETURN-FROM require non-local exit much in the same way that lisp's TRY/CATCH do.
The alternative to this approach is to exit from each function in the call stack via a Javascript `return' statement. Unfortunately, the call stack can contain many functions code over which the Parenscript compiler exerts little control, requiring throw as the control transfer mechanism. Thus, in the general case of unknown code on the call stack, there is no means to exit without a throw. I do not view throwing as an ugly solution at all, since try/catch was designed for non-local exits of all sorts.
Nonetheless, using try/catch to implement Parenscript features deserves some attention. Programs will need to ensure that they do not use try/catch in a way that interferes with the Parenscript convention. Generally, try/catch blocks should only catch specific exceptions and re-throw PS's exceptions. I'm happy to also
implement
a safe TRY/CATCH wrapper that re-throws Parenscript errors and
catches
everything else, too. However, we may want to make an official interface change to try/catch if any lisp-style non-local exit code becomes part of the language.
I present an example of why try/catch is unavoidable inline below:
On Fri, Apr 9, 2010 at 3:42 PM, Vladimir Sedach vsedach@gmail.com wrote:
Makes sense to me. I'll add this to my todo list (which I'll
publish
in an email as soon as I'm done my current work on the PS
compiler).
Vladimir
2010/4/9 Daniel Gackle danielgackle@gmail.com: > I just pushed a patch (authored by Scott) to implement JS's LABEL > and > BREAK > in PS. (Note that this patch deprecates LABELED-FOR since you can > get > the > same effect by combining LABEL and FOR. Was anybody using > LABELED-FOR?) > Here's an example: > (label scope > (foo) > (when (bar) > (break scope)) > (blee)) > => > scope: { > foo(); > if (bar()) { > break scope; > }; > blee(); > }; > I was astonished to discover recently that JS has supported this > ability all > along in the form of labeled statements and labeled breaks. I'd > always > assumed that to get explicit returns from an arbitrary scope,
you'd
> have to > resort to the ugly hack of muscling TRY/CATCH to do it, thinking > that > this > was the closest JS counterpart. > (See http://news.ycombinator.com/item?id=793092 for a thread in > which > several people believe this.) But it appears we were all wrong. > What's not clear yet is how far this can be taken. Can you use it > inside a > nested local function to return immediately from the top-level > function? > That is one thing I've wanted for a long time. > In the ideal case, LABEL/BREAK could be used as a base for > implementing > a > proper BLOCK and RETURN-FROM in PS, something which we'd long > believed > to be > impossible. One challenge is that in CL, RETURN-FROM can take a > value, > which > becomes the value of BLOCK. In other words, BLOCK in CL is an > expression > while LABEL in JS is not. It seems, though, that most of this > challenge > has > already been conquered with the development of implicit return in > PS. > The > only thing you'd need to add is detecting when BLOCK is being used > as > an > expression, declaring a gensymed variable and assigning whatever
is
> happening inside BLOCK to that variable (much like implicit return > already > does with e.g. CASE), then put the variable in the expression > position > that > BLOCK was in. It seems like this ought to work. It would also make > things > like this possible in PS: > (1+ (case foo > (:eleven 11) > (:twelve 12))) > Vladimir (and everybody), is the above clear? What do you think of > it?
As stated above, I think try/catch is the way to go. There is no other way to exit a stack of functions in the general case otherwise.
For example, I can write a Javascript function that calls its
argument
infinity times and never returns.
function mapForever(fn) { while(true) fn(); }
Now consider some parenscript:
(block non-local (map-forever (lambda () (return-from non-local "we got out!"))))
To extricate itself from map-forever, there is no alternative but
JS's
throw statement.
Even if we had the ability to alter every function in the system, it would be necessary to inspect nearly every function call's return values to properly unwind the stack to the appropriate BLOCK.
Having said all that, there are cases when try/catch is not necessary for BLOCK/RETURN-FROM, as you have described. BLOCK should emit code according to the contexts in which RETURN-FROM appears. If there is
a
RETURN-FROM inside the same function, BLOCK can use a label for a local exit. If RETURN-FROM appears inside a lambda, try/catch is necessary (except in cases where you want to optimize this away by inspecting how that lambda gets passed around). If there are no return-froms, just emit a PROGN.
My solution does not do the local optimization, but it does refrain from putting try/catches around code with no return-froms.
Red
> Daniel > _______________________________________________ > parenscript-devel mailing list > parenscript-devel@common-lisp.net > http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel > >
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel@common-lisp.net