(I get bounced if the reply includes too much of prev thread, so am truncating to immediate predecessor.)
I think we should avoid foo.mv and just pass the array as an implicit arg (see below). First a couple responses:
< I tried it out in FF and CL-JS and arguments.callee always seems to be the function object, even for lambdas. >
We may have crossed a wire here. When you said, "You can do that with a global table instead of setting a property on the function object," I thought you had in mind a global table keyed by function *name*, which is why I asked about lambdas since they have no names. JS won't let you use a function object as a key so one would have to concoct some naming scheme.
< In this second call to foo, foo.mv is still the same multiple value array. >
Ok, I get it now. Interestingly, this is the stack problem all over again. Foo.mv may not be global but it's global per-function, so it fails to work when foo is in the stack more than once.
I still strongly favour your array-passing proposal - namely that when compiling a MULTIPLE-VALUE-BIND, PS would pass an extra argument in the function call: a mutable array to hold any extra return values; and when compiling a VALUES, PS would generate code to check whether that array was passed and populate it if so.
But I said something that I now believe to be false: that such an implicit argument "couldn't very well go anywhere [other than the first position] because of things like &REST". Maybe we can sneak it into the last position without disturbing anybody else.
Let PS declare a global sentinel value:
var MV_SENTINEL = {}
This can't accidentally be equal to anything else so we can use it to unambiguously indicate MV-ness; specifically, we can pass it to indicate that the succeeding arg is an MV array. A form like:
(multiple-value-bind (x y) (foo a b) ...body...)
could compile to something like:
var values = []; x = foo(a, b, MV_SENTINEL, values); if (values.length > 0) { y = values[0]; }
And where the values are returned:
(defun foo (x y) (values x y))
could compile to something like:
function foo (x, y) { var values = (arguments[2] === MV_SENTINEL) ? arguments[3] : undefined; if (values) { values[0] = y; } return x; }
This is clean and stack-friendly. Callers that don't care about MV don't have to deal with it (non-PS functions in particular are fine); callers that want MV take on implicit arguments to get it. It's stateless and seems easy to implement. No try/finally and no hacking callee or caller.
At first I thought this wouldn't work because it would collide with &REST and &KEY, which already interpret the arguments list. But why should it? It's PS that generates the code to bind arguments to &REST and &KEY params. For functions that include both a VALUES and a &REST/&KEY, PS can just generate slightly smarter code that checks for MV_SENTINEL and, if it's present, avoids binding it or its successor as part of &REST or &KEY.
Moreover, if it worked for MV, the sentinel idea could be used to tag anything else we wanted into the call. Seems like that could be pretty powerful.
Where does this break?
Daniel
p.s. There's also a way to communicate exactly how many return values are desired, short-circuiting any computation that might be needed to generate the full VALUES list. (I think this point is independent of the above idea but I'll adapt the same examples.) Suppose we have:
(defun foo (x y) (values x y (blah))
but a caller doesn't want that third return value:
(multiple-value-bind (x y) (foo a b) ...body...)
The caller could signal how much it wants like this:
var values = [true]; // fill up to number of values desired, in this case 1 x = foo(a, b, MV_SENTINEL, values); if (typeof values[0] !== undefined) { y = values[0]; }
... and foo could do something like:
function foo (x, y) { var values = (arguments[2] === MV_SENTINEL) ? arguments[3] : undefined; if (values) { values[0] = values[0] ? y : undefined; values[1] = values[1] ? blah() : undefined; } return x; }
On Sat, Sep 1, 2012 at 3:48 PM, Vladimir Sedach vsedach@gmail.com wrote:
But could the compiled PS instead keep a global RETURN_VALUES stack? i.e. a list of MV arrays that callers would push/pop as appropriate? I haven't thought about how this might work and it feels like it probably wouldn't, but I'd like to know why.
You're right that it would need to be a stack, hence the prev_mv variable in the code I posted previously. But you'd still need to associate the values with the function that returned them, and if you can do that by setting a property on the function object, why bother with global variables and tables that hold stacks? The former approach is less code.
I like the array-passing idea and agree that it probably needs to be passed out of band - but can we state explicitly why? For example, why can't we make it a hidden first argument (it couldn't very well go anywhere else because of things like &REST) and make Parenscript smart enough to add in the correct value for that hidden argument every place that function is called (i.e. pass null if the extra return values aren't to be bound, and an array to hold them if they are)?
One obvious drawback is that non-PS functions wouldn't be able to call such a function normally; they'd have to know about the extra arg. What other drawbacks are there?
You wouldn't be able to have calls to these functions before they're defined in your code.
I like this idea, because everyone in the JS world is so adamant that one shouldn't mess with arguments.metablah (though arguments.callee has got to be better than arguments.callee.caller). But how would it work for lambdas?
I tried it out in FF and CL-JS and arguments.callee always seems to be the function object, even for lambdas.
Right. I don't follow your example here, though, so I wonder if you can spell it out a bit further.
foo is a function that returns either one value or multiple values. We call foo expecting to receive multiple values. Now foo.mv is set to the multiple value array. In the course of computation, foo calls bar, which does not care about multiple values. In this second call to foo, foo.mv is still the same multiple value array. The second call to foo returns multiple values, so the multiple value array gets filled up. However, the first call to foo returns only one value. But because the multiple value array has been filled up, we get bogus multiple values.
Vladimir