[parenscript-devel] A simpler way to do multiple value return? (Was: PS test failures in 7be9b45)
Red Daly
reddaly at gmail.com
Wed Aug 29 16:16:41 UTC 2012
Hi Daniel,
I'm glad to be part of the discussion :)
On Aug 28, 2012 8:53 PM, "Daniel Gackle" <danielgackle at gmail.com> wrote:
> Hi Red,
>
> I was hoping you'd chime in. I'll see your scenario 3 and raise you a
> 3a and a 3b:
>
> Scenario 3a: A non-Parenscript function that calls a mv-returning
> Parenscript function but only needs its first return value;
>
> Scenario 3b: A non-Parenscript function that calls a mv-returning
> Parenscript function and needs all its return values.
>
> 3a works fine as long as the MV implementation is careful to use a
> normal JS return to pass the first return value back to the caller.
> That's true both of what PS does today and of the global-var proposal.
>
> As for 3b (the scenario, not the hacker!), seems to me this can't work
> at all and there's no need to support it. If you're a non-PS function
> then by definition you can't use PS's MULTIPLE-VALUE-BIND to access
> the additional return values because the MV construct only exists in
> PS. I suppose if you really wanted to you could manually write JS to
> do whatever PS does to supply those values to the caller, but then
> you're not really a non-PS function anymore, so much as a
> manually-compiled PS function.
>
> Daniel
>
I wasn't clear in my original post. I'm not concerned with the
non-Parenscript function's ability to receive multiple values. I'm
concerned that the non-Parenscript function will interfere with the
multiple value return.
If a non-Parenscript function calls a Parenscript function that messes with
the global variables, the non-Parenscript function could end up returning
stuff unintentionally. This is because the non-Parenscript function will
not manipulate the global variables to clean up the unused multiple values
that are returned.
Here's some code to illustrait the point:
(defun ps-foo ()
(multiple-value-bind (a b) (bar)
(+ a b)))
(defun ps-fn-returns-mv ()
(values 1 2))
function barWorks() {
return psFnReturnsMv();
}
function barBreaks() {
psFnReturnsMv(); // global variables for MV returning get set up and
linger
// return a single value: 42
return 42;
}
Let's assume the two Parenscript functions translate into something like
this:
var RETURN_VALUES = null;
var MV_CALL = false;
... other global variables related to multiple values
function psFoo() {
// global variable stuff
MV_CALL = true;
RETURN_VALUES = null;
var a = bar();
MV_CALL = false;
var b = RETURN_VALUES ? RETURN_VALUES[0] : undefined;
return a + b;
}
function psFnReturnsMv() {
if (MV_CALL)
RETURN_VALUES = [ 2 ];
return 1;
}
When bar = barWorks, foo will return 1 + 2, as intended. When bar =
barBreaks, foo will incorrectly return 42 + 2 because the global variables
were not properly cleaned up.
I hope this makes sense.
- Red
>
>
> On Tue, Aug 28, 2012 at 8:46 PM, Red Daly <reddaly at gmail.com> wrote:
>
>> On Tue, Aug 28, 2012 at 7:20 PM, Vladimir Sedach <vsedach at gmail.com>wrote:
>>
>>> The counter-example to using a global variable as I remember it from
>>> the last discussion was this case:
>>>
>>
>> The last discussion:
>> http://lists.common-lisp.net/pipermail/parenscript-devel/2009-October/000639.html
>>
>>>
>>> (defun blah ()
>>> (values 1 2 3))
>>>
>>> (defun foo ()
>>> (blah)
>>> (some-random-js-function))
>>>
>>> (defun bar ()
>>> (multiple-value-bind (a b c) (foo)
>>> (+ a b c)))
>>>
>>> Now a call to bar returns 6, when it shouldn't. I think it might be
>>> possible to use another global variable as a flag that's set and
>>> checked by multiple-value aware PS functions, but I need to think
>>> about how to make it work.
>>>
>>> Vladimir
>>>
>>> On Tue, Aug 28, 2012 at 10:07 PM, Daniel Gackle <danielgackle at gmail.com>
>>> wrote:
>>> > Those test failures make sense now - thanks. Also, this comment
>>> > reminded me of something:
>>> >
>>> > < the callee.caller property for functions, which multiple value return
>>> > depends on >
>>> >
>>> > I dislike our implementation for multiple value return. Stuffing the
>>> > values in callee.caller is complicated and doesn't feel right (I think
>>> > I may have been the one who came up with it; it was a bad idea), plus
>>> > it relies on one of the shakiest aspects if not of JS itself than
>>> > certainly of the JS implementations.
>>> >
>>> > A simpler way occurred to me the other day and I'd like to know where
>>> > it breaks. The argument goes like this: since JS is single-threaded
>>> > and functions have to return synchronously, there can be only one
>>> > function return in play at any given time, therefore there can be only
>>> > one multiple-return-value list at any time, therefore why not just
>>> > store it in a global variable?
>>>
>>
>> My guess at why your proposal won't work: It ignores knowledge of the
>> call stack, which is practically necessary.
>>
>> Imagine a form (multiple-values-bind (a b c ...) (FOO ...)). Any
>> solution of this with global variables will take the form:
>>
>> step 1: Do some stuff with global variable manipulation
>> step 2: Call FOO
>> step 3: Do some stuff to clean up.
>>
>> Pretty much any code can execute inside FOO. This includes a few
>> scenarios:
>>
>> Scenario 1: A simple JS function, native or not, that doesn't call
>> anything that returns multiple values. Your solution works.
>> Scenario 2: A Parenscript function, written to manipulate your global
>> variables properly. Your solution works.
>> Scenario 3: A non-Parenscript function that calls a mv-returning
>> Parenscript function.
>>
>> Can your solution handle Scenario 3? My guess is it cannot because it
>> ignores the call stack.
>>
>> Just my quick guess.
>>
>> - Red
>>
>>
>>
>>> >
>>> > Say this variable is called *spillover*. Then this:
>>> >
>>> > (defun blah ()
>>> > (values 1 2 3))
>>> >
>>> > (defun callblah ()
>>> > (multiple-value-bind (a b c) (blah)
>>> > (+ a b c)))
>>> >
>>> > ...might compile to:
>>> >
>>> > function blah() {
>>> > SPILLOVER = [2, 3];
>>> > return 1;
>>> > };
>>> > function callblah() {
>>> > var a = blah();
>>> > var b = SPILLOVER[1];
>>> > var c = SPILLOVER[2];
>>> > return a + b + c;
>>> > };
>>> >
>>> > There might be complicating factors that would make the JS more
>>> > involved in practice, but I don't remember what they are.
>>> >
>>> > Apart from being so much simpler, this implementation has two
>>> > advantages. First, it's "morally" better in Eugenia Cheng's sense
>>> > (http://cheng.staff.shef.ac.uk/morality/morality.pdf). The multiple
>>> > return values don't belong to caller or callee, but to the call
>>> > itself. Caller and callee are functions that persist across many calls
>>> > and ought not to have information about a specific call attached to
>>> > them. That's why PS is forced to add ugly code to save the previous
>>> > value attached to callee and restore it using a try/finally at the end.
>>> >
>>> > Second, it would fix a known problem. PS breaks when you have an
>>> > interloper like FOO here:
>>> >
>>> > (defun blah ()
>>> > (values 1 2 3))
>>> >
>>> > (defun foo ()
>>> > (blah))
>>> >
>>> > (defun bar ()
>>> > (multiple-value-bind (a b c) (foo)
>>> > (+ a b c)))
>>> >
>>> > BAR should return 6, and does in CL, but in PS it returns 1, because
>>> > FOO doesn't return multiple values, so B and C are null and "1 + null +
>>> > null"
>>> > is 1 in JS. But the *spillover* hack would make BAR return 6.
>>> >
>>> > Could we get away with this, or what am I missing?
>>> >
>>> > Daniel
>>> >
>>> >
>>> > On Tue, Aug 28, 2012 at 6:00 PM, Vladimir Sedach <vsedach at gmail.com>
>>> wrote:
>>> >>
>>> >> Hi Daniel,
>>> >>
>>> >> Yes, those two failures in the eval test suite are expected.
>>> >> CL-JavaScript doesn't have the callee.caller property for functions,
>>> >> which multiple value return depends on. I wasn't sure where to comment
>>> >> those tests out, so I left them in to remind myself to add
>>> >> callee.caller to CL-JavaScript (I've already talked to Marijn
>>> >> Haverbeke about that).
>>> >>
>>> >> Thank you,
>>> >> Vladimir
>>> >>
>>> >> On Mon, Aug 27, 2012 at 11:58 PM, Daniel Gackle <
>>> danielgackle at gmail.com>
>>> >> wrote:
>>> >> > I've rebased my PS LOOP extensions [1] onto the latest commit
>>> >> > (7be9b45) and recompiled Skysheet. The generated JS looks fine.
>>> There
>>> >> > was one glitch that I'll report separately along with a workaround.
>>> >> > Before pushing the LOOP extensions onto master, though, I want to
>>> >> > update any relevant PS tests. Some will fail because the LOOP output
>>> >> > has changed quite a bit. Unfortunately I'm also seeing failures
>>> when I
>>> >> > run the tests in 7be9b45, which is prior to any of these LOOP
>>> >> > changes. I've pasted the output below [2]. It doesn't look like
>>> these
>>> >> > failures are related to work in ps-loop.lisp, so I'll just ignore
>>> them
>>> >> > for the time being, but Vladimir can you please comment on whether
>>> you
>>> >> > know about them or whether there's something unexpected going on?
>>> >> >
>>> >> > Daniel
>>> >> >
>>> >> > [1] These are the constructs FOR..OF and MAP..TO, plus a change to
>>> >> > FOR..ON, that I described in my email to this list on April 11.
>>> They
>>> >> > are currently sitting in the "loop" branch. Rebasing them was
>>> >> > nontrivial because of Boris' additions to ps-loop.lisp, but it seems
>>> >> > to have all gone ok. Boris, if you're reading this, please look out
>>> for
>>> >> > any
>>> >> > regressions once I push these changes and let us know if you notice
>>> >> > anything.
>>> >> >
>>> >> > [2] Running output tests:
>>> >> >
>>> >> >
>>> ........................................................................................................................................................................................................................................................................................................................................................................................................................................
>>> >> > Did 424 checks.
>>> >> > Pass: 424 (100%)
>>> >> > Skip: 0 ( 0%)
>>> >> > Fail: 0 ( 0%)
>>> >> > Running package system tests:
>>> >> > .........
>>> >> > Did 9 checks.
>>> >> > Pass: 9 (100%)
>>> >> > Skip: 0 ( 0%)
>>> >> > Fail: 0 ( 0%)
>>> >> > Running CL-JavaScript eval tests:
>>> >> > ...........................f...............X......................
>>> >> > Did 66 checks.
>>> >> > Pass: 64 (96%)
>>> >> > Skip: 0 ( 0%)
>>> >> > Fail: 2 ( 3%)
>>> >> > Failure Details:
>>> >> > --------------------------------
>>> >> > mv-return1 []:
>>> >> > Unexpected Error: #<cl-js:js-condition #x30200155257D>
>>> >> > [js] TypeError: undefined has no properties...
>>> >> > --------------------------------
>>> >> > --------------------------------
>>> >> > dynamic-extent-function-return-values []:
>>> >> > (funcall (if (typep #:g36204 'structure-object) #'equalp
>>> #'equal)
>>> >> > #:g36204 (jsarray '(1 2 3))) was NIL..
>>> >> > --------------------------------
>>> >> >
>>> >> >
>>> >> > _______________________________________________
>>> >> > parenscript-devel mailing list
>>> >> > parenscript-devel at common-lisp.net
>>> >> >
>>> http://lists.common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
>>> >> >
>>> >>
>>> >> _______________________________________________
>>> >> parenscript-devel mailing list
>>> >> parenscript-devel at common-lisp.net
>>> >>
>>> http://lists.common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
>>> >
>>> >
>>> > Those test failures make sense now - thanks. Also, this comment
>>> > reminded me of something:
>>> >
>>> > < the callee.caller property for functions, which multiple value return
>>> > depends on >
>>> >
>>> > I dislike our implementation for multiple value return. Stuffing the
>>> > values in callee.caller is complicated and doesn't feel right (I think
>>> > I may have been the one who came up with it; it was a bad idea), plus
>>> > it relies on one of the shakiest aspects if not of JS itself than
>>> > certainly of the JS implementations.
>>> >
>>> > A simpler way occurred to me the other day and I'd like to know where
>>> > it breaks. The argument goes like this: since JS is single-threaded
>>> > and functions have to return synchronously, there can be only one
>>> > function return in play at any given time, therefore there can be only
>>> > one multiple-return-value list at any time, therefore why not just
>>> > store it in a global variable?
>>> >
>>> > Say this variable is called *spillover*. Then this:
>>> >
>>> > (defun blah ()
>>> > (values 1 2 3))
>>> >
>>> > (defun callblah ()
>>> > (multiple-value-bind (a b c) (blah)
>>> > (+ a b c)))
>>> >
>>> > ...might compile to:
>>> >
>>> > function blah() {
>>> > SPILLOVER = [2, 3];
>>> > return 1;
>>> > };
>>> > function callblah() {
>>> > var a = blah();
>>> > var b = SPILLOVER[1];
>>> > var c = SPILLOVER[2];
>>> > return a + b + c;
>>> > };
>>> >
>>> > There might be complicating factors that would make the JS more
>>> > involved in practice, but I don't remember what they are.
>>> >
>>> > Apart from being so much simpler, this implementation has two
>>> > advantages. First, it's "morally" better in Eugenia Cheng's sense
>>> > (http://cheng.staff.shef.ac.uk/morality/morality.pdf). The multiple
>>> > return values don't belong to caller or callee, but to the call
>>> > itself. Caller and callee are functions that persist across many calls
>>> > and ought not to have information about a specific call attached to
>>> > them. That's why PS is forced to add ugly code to save the previous
>>> > value and restore it using a try/finally at the end.
>>> >
>>> > Second, it would fix a known problem. PS breaks when you introduce an
>>> > interloper like FOO here:
>>> >
>>> > (defun blah ()
>>> > (values 1 2 3))
>>> >
>>> > (defun foo ()
>>> > (blah))
>>> >
>>> > (defun bar ()
>>> > (multiple-value-bind (a b c) (foo)
>>> > (+ a b c)))
>>> >
>>> > BAR should return 6, and does in CL, but in PS it returns 1, because
>>> > FOO doesn't return multiple values, so B and C are null and "1 + null +
>>> > null"
>>> > is 1 in JS. But the *spillover* hack would make BAR return 6.
>>> >
>>> > Could we get away with this, or what am I missing?
>>> >
>>> > Daniel
>>> >
>>> >
>>> > _______________________________________________
>>> > parenscript-devel mailing list
>>> > parenscript-devel at common-lisp.net
>>> >
>>> http://lists.common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
>>> >
>>>
>>> _______________________________________________
>>> parenscript-devel mailing list
>>> parenscript-devel at common-lisp.net
>>> http://lists.common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
>>>
>>
>>
>> _______________________________________________
>> parenscript-devel mailing list
>> parenscript-devel at common-lisp.net
>> http://lists.common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
>>
>>
>
> _______________________________________________
> parenscript-devel mailing list
> parenscript-devel at common-lisp.net
> http://lists.common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mailman.common-lisp.net/pipermail/parenscript-devel/attachments/20120829/4ed98f44/attachment.html>
More information about the parenscript-devel
mailing list