[parenscript-devel] A simpler way to do multiple value return? (Was: PS test failures in 7be9b45)

Daniel Gackle danielgackle at gmail.com
Sun Sep 2 03:59:41 UTC 2012


(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 at 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
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mailman.common-lisp.net/pipermail/parenscript-devel/attachments/20120901/b59869c5/attachment.html>


More information about the parenscript-devel mailing list