[parenscript-devel] Multiple value calls
Red Daly
reddaly at gmail.com
Wed Nov 25 09:11:55 UTC 2009
On Thu, Nov 5, 2009 at 10:43 AM, Daniel Gackle <danielgackle at gmail.com> wrote:
> < Why do you say some of the time? Under what cases would it break? >
>
> Red will think of something :)
>
Well, I think this implementation is quite close and would work with
beefier semantic analysis.
> < I think you should push this code as a patch; let's see what happens. >
>
> I'll do that, perhaps once Scott has our code working with the implicit
> return stuff, to minimize upgrade inconvenience.
>
> < The ability to attach arbitrary properties to function objects finally
> comes in useful. >
>
> This is actually the one feature of JS that I miss in CL: the ability to
> attach arbitrary properties to pretty much anything. It is a godsend for
> exploratory programming since it eliminates the gruntwork of packing things
> into defined structures (objects, arrays, whatever) which you then have to
> destructure to get at them. It's an extreme of loosey-gooseyness that must
> give the static bondage people nightmares (a feature, not a bug). I wish
> someone would figure out a way to hack the same ability into CL (we've
> already done so in a limited, good-enough-for-our-app way). It's interesting
> that symbol-plists just aren't that useful in this regard.
>
> Daniel
>
>
> On Wed, Nov 4, 2009 at 10:16 PM, Vladimir Sedach <vsedach at gmail.com> wrote:
>>
>> > Ok, here's a variation that abandons global MV altogether and stores it
>> > instead as a property on the caller.
>>
>> Oh, wow. The ability to attach arbitrary properties to function
>> objects finally comes in useful. Very clever!
>>
>> > It passes all the previously mentioned
>> > cases and works at least some of the time with anonymous functions.
>>
>> Why do you say some of the time? Under what cases would it break?
>>
>> > (defpsmacro values (main &rest additional)
>> > (with-ps-gensyms (mv)
>> > `(let ((,mv (list , at additional)))
>> > (when (defined (@ (@ (@ arguments :callee) :caller) :mv))
>> > (setf (@ (@ (@ arguments :callee) :caller) :mv) ,mv))
>> > (return ,main))))
>> >
>> > (defpsmacro multiple-value-bind (vars expr &body body)
>> > (with-ps-gensyms (mv prev)
>> > `(let ((,prev (@ (@ arguments :callee) :mv)))
>> > (try
>> > (progn
>> > (setf (@ (@ arguments :callee) :mv) t)
>> > (let ((,(car vars) ,expr)
>> > (,mv (if (objectp (@ (@ arguments :callee) :mv))
>> > (@ (@ arguments :callee) :mv)
>> > (make-array ,(1- (length vars))))))
>> > (destructuring-bind ,(cdr vars) ,mv
>> > , at body)))
>> > (:finally (if (undefined ,prev)
>> > (delete (@ (@ arguments :callee) :mv))
>> > (setf (@ (@ arguments :callee) :mv) ,prev)))))))
I think this breaks in the case where EXPR in the above macro has more
than one form:
(multiple-value-bind (a b)
(progn
(returns-mv)
(doesnt))
(alert a)
(alert b))
Where (defun returns-mv() (values 1 2)) and (defun doesnt () (return 3)).
(As an aside, the evaluation order of the MV arguments is not serial:
(values (foo) (bar)) evaluates (bar) first. This is a simple bug that
the ONCE-ONLY macro should solve)
This is so close, and I think attaching MV information to the caller
is a viable solution, since (1) MV returns only make sense for
parenscript functions, not functions written by others, and (2) with
just a little bit more semantic analysis we should be able to derive
the form that will be responsible for returning the values bound by
multiple-value-bind. So good work! This solution will stand for now.
Note, however, that this solution requires every implicit return (e.g.
a tail call) to execute the multiple-value machinery. It's probably
not a big deal except for certain sections of code, and hey, it's
Javascript anyway.
Red
>>
>> I think you should push this code as a patch; let's see what happens.
>>
>> Vladimir
>>
>> > Specifically, it passes the case in Red's email that broke my previous
>> > attempt. That is, given ADD-TO-RESULT as defined in that earlier email,
>> >
>> > (defun foo ()
>> > (multiple-value-bind (a b) (add-to-result (lambda (x) (values 1 10))
>> > 2)
>> > (return (list a b))))
>> >
>> > foo() now correctly evaluates to [3,undefined] instead of [3,10].
>> >
>> > Can you guys come up with a new case to break it?
>> >
>> > Daniel
>> >
>> >
>> >
>> > On Sun, Nov 1, 2009 at 1:41 PM, Red Daly <reddaly at gmail.com> wrote:
>> >>
>> >>
>> >> On Sun, Nov 1, 2009 at 9:50 AM, Daniel Gackle <danielgackle at gmail.com>
>> >> wrote:
>> >>>
>> >>> It might help to use a PS special variable and make
>> >>> MULTIPLE-VALUE-BIND
>> >>> responsible for cleanup.
>> >>>
>> >>> (ps (defvar *mv* undefined))
>> >>>
>> >>> (defpsmacro values (main &rest additional)
>> >>> (with-ps-gensyms (mv)
>> >>> `(let ((,mv (list , at additional)))
>> >>> (when (defined *mv*)
>> >>> (setf *mv* ,mv))
>> >>> (return ,main))))
>> >>>
>> >>> (defpsmacro multiple-value-bind (vars expr &body body)
>> >>> `(let ((*mv* '()))
>> >>> (let ((,(car vars) ,expr))
>> >>> (destructuring-bind ,(cdr vars) *mv*
>> >>> , at body))))
>> >>>
>> >>> This works in the obvious cases. I'm not sure it handles Red's
>> >>> scenarios.
>> >>> Red, can you supply an example where this breaks?
>> >>
>> >>
>> >> The main case I am concerned about is dealing with non-parenscript
>> >> functions that will not manipulate the mv state. Something like the
>> >> following would break the above code:
>> >>
>> >> (defun foo ()
>> >> (let ((my-callback (lambda (x) (values 1 10)))
>> >> (multiple-value-bind (a b)
>> >> (add-to-result my-callback 2)))
>> >>
>> >> =>
>> >>
>> >> // imagine this is a non-Parenscript function that we cannot manipulate
>> >> with the
>> >> // Parenscript compiler
>> >> function addToResult(callback, x) {
>> >> // returns the result of adding x to the result of calling the
>> >> callback
>> >> return callback() + x;
>> >> }
>> >>
>> >> function foo () {
>> >> var myCallback = function (x) {
>> >> if (MV !== undefined)
>> >> MV = [10];
>> >> return 1;
>> >> };
>> >>
>> >> var old_MV = MV; // begin let
>> >> MV = null;
>> >> var a = addToResult(myCallback, 2);
>> >> var b = MV ? MV[0] : null;
>> >>
>> >> // now b === 10 but it should be nil
>> >> // this is because addToResult did not reset MV
>> >>
>> >> MV = old_MV; // end let
>> >>
>> >> }
>> >>
>> >> This is why I think you might need to identify the callee in another
>> >> special variable, and for emitted functions reset the MV_CALLEE
>> >> variable
>> >> before returning (and maybe in other places?).
>> >>
>> >>
>> >> Red
>> >>
>> >>
>> >>
>> >>>
>> >>> Daniel
>> >>>
>> >>> p.s. I took the easy way out of making VALUES always prepend RETURN,
>> >>> but
>> >>> once Vladimir bestows implicit RETURN upon us we can take that out ;)
>> >>>
>> >>>
>> >>> On Sat, Oct 31, 2009 at 9:53 PM, Red Daly <reddaly at gmail.com> wrote:
>> >>>>
>> >>>> I apologize for sending the first half of this email in error
>> >>>> earlier:
>> >>>>
>> >>>> On Sat, Oct 31, 2009 at 8:35 PM, Red Daly <reddaly at gmail.com> wrote:
>> >>>>>
>> >>>>> Hi Parenscripters,
>> >>>>>
>> >>>>> As far as I can tell, multiple-value function calls are a unique
>> >>>>> feature of lisp. I would like the ability to perform multiple-value
>> >>>>> calls
>> >>>>> in Parenscript but I don't know if a sane solution exists or not.
>> >>>>>
>> >>>>> Can anyone come up with a scheme for returning multiple values that
>> >>>>> translates well to Javascript? Ideally such a scheme would not
>> >>>>> introduce
>> >>>>> much overhead for usual functions that do not use or return multiple
>> >>>>> values
>> >>>>> (though perhaps setting some sort of global MV flag might be
>> >>>>> inexpensive
>> >>>>> enough). Functions that return multiple values should also only
>> >>>>> appear to
>> >>>>> return a single value when called by a function that expects only
>> >>>>> one return
>> >>>>> value (including native javascript functions).
>> >>>>>
>> >>>>> (defun paren-mv-returner ()
>> >>>>> (return (values 1 2 3)))
>> >>>>>
>> >>>>> =>
>> >>>>>
>> >>>>>
>> >>>>> function parenMvReturner() {
>> >>>>> /* do some magic with the values 2 and 3 */
>> >>>>> return 1;
>> >>>>> }
>> >>>>>
>> >>>>> // one implementation might be
>> >>>>> var mv = undefined;
>> >>>>>
>> >>>>> function parenMvReturner() {
>> >>>>> mv = [2, 3];
>> >>>>> return 1;
>> >>>>> }
>> >>>>>
>> >>>>> // this scheme needs to adjust the return statement of every
>> >>>>> function
>> >>>>> so it might not be sufficient
>> >>>>> // consider this other function
>> >>>>>
>> >>>>> function parenMySingleReturner () {
>> >>>>> var x = parenMvReturner();
>> >>>>
>> >>>> return x;
>> >>>> }
>> >>>>
>> >>>> // parenMySingleReturner() will appear to return multiple values
>> >>>> unless
>> >>>> it modifies the mv value itself
>> >>>>
>> >>>> // correction:
>> >>>> function parenMySingleReturner () {
>> >>>> var x = parenMvReturner();
>> >>>> mv = null;
>> >>>> return x;
>> >>>> }
>> >>>>
>> >>>> But it seems like this solution will fall apart for calls to native
>> >>>> Javascript functions over which we have no control. If we pass a
>> >>>> multiple-value returning function as an argument to a native
>> >>>> function, the
>> >>>> native function will not perform the necessary mv-nulling when it
>> >>>> returns.
>> >>>>
>> >>>> someForeignJavascriptFunction( someMVReturningFunction)
>> >>>>
>> >>>> will return whatever the someForeignJavascriptFunction should return,
>> >>>> but it will also appear to return the other values that
>> >>>> someMVReturningFunction set in the mv variable, since
>> >>>> someForeignJavascriptFunction performs no cleanup.
>> >>>>
>> >>>> Maybe this limitation can be avoided by having an mv-returning
>> >>>> function
>> >>>> A set a global variable "mvFunctionReturner" equal to the function A
>> >>>> and a
>> >>>> mv-receiver can check that mvFunctionReturner is set according to the
>> >>>> function it called expecting multiple values. Does this scheme miss
>> >>>> any
>> >>>> cases?
>> >>>>
>> >>>> Anyway I have thought a little bit about this and I thought I would
>> >>>> pass
>> >>>> it off to the rest of the Parenscripters as a thought experiment.
>> >>>> Assume
>> >>>> you can do a lot more semantic analysis than Parenscript currently
>> >>>> does and
>> >>>> transform the compiled source however you want. But any compiled
>> >>>> functions
>> >>>> must still be able to be treated as normal Javascript functions and
>> >>>> all and
>> >>>> only functions that should return multiple values appear to return
>> >>>> them.
>> >>>>
>> >>>> Cheers,
>> >>>> Red
>> >>>>
>> >>>> _______________________________________________
>> >>>> parenscript-devel mailing list
>> >>>> parenscript-devel at common-lisp.net
>> >>>> http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
>> >>>>
>> >>>
>> >>>
>> >>> _______________________________________________
>> >>> parenscript-devel mailing list
>> >>> parenscript-devel at common-lisp.net
>> >>> http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
>> >>>
>> >>
>> >>
>> >> _______________________________________________
>> >> parenscript-devel mailing list
>> >> parenscript-devel at common-lisp.net
>> >> http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
>> >>
>> >
>> >
>> > _______________________________________________
>> > parenscript-devel mailing list
>> > parenscript-devel at common-lisp.net
>> > http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
>> >
>> >
>>
>> _______________________________________________
>> parenscript-devel mailing list
>> parenscript-devel at common-lisp.net
>> http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
>
>
> _______________________________________________
> parenscript-devel mailing list
> parenscript-devel at common-lisp.net
> http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
>
>
More information about the parenscript-devel
mailing list