[parenscript-devel] 'use strict' and 'with' in loop

Daniel Gackle danielgackle at gmail.com
Sat Mar 23 20:35:16 UTC 2013


I think we'll probably eventually get a strict mode out of Vladimir
and then you won't have to use a forked PS. Out of curiosity, what
is your reason for needing to use strict mode?

> could one not avoid the use of global variables by returning a js object

If you do that naively, then callers who only need the first return
value will no longer be able to call the function normally (without
multiple-value-bind). A non-naive implementation would require the
compiler to know everywhere which values might "really" be wrapped
inside one of those js objects. It's not clear how to do that, and
such implementations tend to get tricky very quickly and break on
corner cases. So, not out of the question but not clear how to make it
really work.

If you're interested in the vicissitudes of implementing multiple
return values on top of JS, Vladimir and I had an extensive discussion
about it here last year. I'd be very interested to hear your opinions.
It's a thorny problem. The current PS implementation is buggy. I
proposed an alternative, but it too has issues. I don't think anyone
has figured this out yet. I used to think that it didn't matter so
much, but after learning how simply and brilliantly Lua makes use of
multiple return values, I changed my mind. (Plus, to change the
subject, one dream of mine is to have a Lisp that compiles to both JS
and Lua, and such a thing must have multiple return.)

Daniel

On Sat, Mar 23, 2013 at 9:37 AM, Peter Wood <p.r.wood at gmail.com> wrote:

> Hi Daniel
>
> You write:
>
> "There's another problem with strict mode and PS: strict mode
> banishes arguments.callee, which PS currently relies on for its
> multiple-return-value implementation."
>
> I hadn't been using multiple-value-bind, so I didn't notice.  I have now
> experimented a bit and yet another problem is that it assigns to an
> undeclared variable __PS_MV_REG which is also not allowed in strict mode.
> In addition, it breaks if one uses a (funcall ...) in the value-form like:
>
> (defun bar ()
>           (multiple-value-bind (x y)
>               (funcall foo 1 2))
>           (ps:array x y)) but that is not related
>
> I am currently using a hacked version of parenscript with ps:*use-strict*
> which determines wether the 'with{}' stuff gets used by the compiler.  I
> reluctantly decided I would have to, as I really need to 'use strict' and I
> ran into a problem with a complicated do* expression in which the compiler
> _also_ used 'with{}'.
>
> With respect to the multiple return values implementation, could one not
> avoid the use of global variables by returning a js object, something like
> the following?
>
> ;;vals is like values
> (ps:defpsmacro vals (&rest args)
>   (let ((idx-args (mapcan (lambda (idx arg)
>                             (list idx arg))
>                           (loop for i from 0 to (1- (length args))
>                                collect i)
>                           args)))
>     `(ps:create , at idx-args)))
>
> ;;mvb is like multiple-value-bind
> (ps:defpsmacro mvb (vars vals-form &body body)
>   (let* ((val-form-name (ps:ps-gensym "vfn_"))
>          (var-idxs (mapcar (lambda (var idx)
>                              (list var `(ps:getprop ,val-form-name ,idx)))
>                            vars
>                            (loop for i from 0 to (1- (length vars))
>                               collect i))))
>     `(let* ((,val-form-name ,vals-form)
>             , at var-idxs)
>        , at body)))
>
>
> On 6 March 2013 21:46, Daniel Gackle <danielgackle at gmail.com> wrote:
>
>> Hi Peter,
>>
>> > Returning the closure from a function works the same in CL and
>>  > parenscript and does not generate the 'with'
>>
>> Yes, because functions create new bindings for their args, which of
>> course suggests that PS might be able to use:
>>
>>   (function (i) { ... })(i)
>>
>> instead of
>>
>>   with ({ i : i }) { ... }.
>>
>> For example, evaluating the following in a PS hacked to
>> remove the 'with' trick produces the desired '(10 20 30):
>>
>> (let ((closures (loop :for i :from 1 :to 3 :collect ((lambda (i) (lambda
>> () (* i 10))) i))))
>>     (loop :for fn :in closures :collect (funcall fn)))
>>
>> Would that trick work correctly everywhere that the 'with' trick does?
>> I'm not sure if one might run into trouble with statements vs.
>> expressions in some cases, but the PS implementation has solved most
>> of those issues, so I'd be surprised if lambdas couldn't do it. You'd
>> arguably take a hit in readability, but might gain something in
>> performance, since 'with' is slow (or used to be). It's also worth
>> noting that since the 'with' trick is there only for closures that
>> capture loop vars, it needn't be the whole loop body that gets wrapped
>> in an extra lambda (the way it currently gets wrapped in 'with'), but
>> only the creation of the closure itself — which is certainly just an
>> expression.
>>
>> Hopefully Vladimir will eventually chime in on this (he's been busy
>> working in LA lately, but we expect he'll get back to open source
>> at some point.)
>>
>> There's another problem with strict mode and PS: strict mode
>> banishes arguments.callee, which PS currently relies on for its
>> multiple-return-value implementation.
>>
>> I'm glad you raised this question. It seems like PS ought to be able
>> to emit strict code on demand, if not by default. One of PS's goals
>> has always been to produce efficient JS, and one of the reasons for
>> strict mode is to support optimization. Moreover, the annoyance of
>> having to declare strict mode in JS scripts or functions is alleviated
>> by having a compiler like PS do it for you. A nice touch would be to
>> leverage the DECLARE syntax to get this at the function level.
>>
>> Daniel
>>
>>
>> On Sat, Mar 2, 2013 at 1:47 AM, Peter Wood <p.r.wood at gmail.com> wrote:
>>
>>> Hi Daniel
>>>
>>>
>>> Thanks for the nice explanation.  It's a cool trick (although personally
>>> I would prefer it if parenscript followed CL's scoping).  You can get the
>>> desired '(10 20 30) behaviour in CL (I'm using SBCL) like this:
>>>
>>>
>>>  (let ((closures (loop :for i :from 1 :to 3
>>>                     :collect (let* ((j i)
>>>                                     (cl (lambda () (* j 10))))
>>>                                cl))))
>>>
>>>    (loop :for fn :in closures :collect (funcall fn)))
>>>
>>> ==> (10 20 30)
>>>
>>> However, when I tested this in a parenscript hacked not to generate the
>>> 'with ({i : i})' it didn't work (the generated js returns '[30, 30, 30]'.
>>> It _does_ work in the vanilla parenscript, but also generates the offending
>>> 'with ({j . null})'.  Returning the closure from a function works the same
>>> in CL and parenscript and does not generate the with object stuff.
>>>
>>> (defun foo ()
>>>            (labels ((make-cl (i) (lambda () (* i 10))))
>>>
>>>              (let ((closures (loop :for i :from 1 :to 3
>>>                                 :collect (make-cl i))))
>>>                (loop :for fn :in closures :collect (funcall fn)))))
>>>
>>> (foo) ==> (10 20 30)
>>>
>>> foo() ==> [10, 20, 30]
>>>
>>> So, apart from using do, there is also a labels 'workaround' when I want
>>> to 'use strict';   Whatever you guys decide, thanks again for parenscript.
>>> It's excellent and fun as well!
>>>
>>> Regards,
>>> Peter
>>>
>>>
>>>
>>>
>>>
>>> On 2 March 2013 02:08, Daniel Gackle <danielgackle at gmail.com> wrote:
>>>
>>>> Hi Peter,
>>>>
>>>> Welcome! The 'with' trick (which confused me when I first saw it as
>>>> well) occurs when a loop contains a closure that captures the loop
>>>> iteration variable:
>>>>
>>>>   (loop :for i :from 1 :to 3 :collect (lambda () (* i 10)))
>>>>
>>>> The question is what value of i each lambda should use when it's
>>>> called. The 'with' trick establishes a new scope with a new binding
>>>> for 'i' inside the loop body and puts the closure inside that scope.
>>>> Thus, if you do this:
>>>>
>>>>   (let ((closures (loop :for i :from 1 :to 3 :collect (lambda () (* i
>>>> 10)))))
>>>>     (loop :for fn :in closures :collect (funcall fn)))
>>>>
>>>> ... you get '(10 20 30), because each closure remembers the value that
>>>> i had when it was created. Without the 'with' trick, you'd get '(40 40
>>>> 40),
>>>> because the closures all share the loop's original binding for i, and
>>>> that held 40 by the time the loop terminated.
>>>>
>>>> The fact that it breaks Strict mode, though, means that either PS's
>>>> implementation should change, at least to offer the option of not
>>>> using it, or drop the trick altogether. I have a feeling the latter
>>>> would be simplest. For one thing, Common Lisp, which is PS's
>>>> touchstone, doesn't have this scoping behavior. In CCL I get '(40 40 40)
>>>> for the above expression. And DOTIMES is the same:
>>>>
>>>>   (let ((list nil))
>>>>      (dotimes (i 3) (push (lambda () (* i 10)) list))
>>>>      (mapcar #'funcall (reverse list)))
>>>>
>>>> => (30 30 30)
>>>>
>>>> So this is a case of plus royaliste que le roi that could arguably just
>>>> be
>>>> abandoned. If not, though, a special variable for Strict Mode would be
>>>> a good idea. Vladimir?
>>>>
>>>> Daniel
>>>>
>>>>
>>>> On Fri, Mar 1, 2013 at 12:39 PM, Peter Wood <p.r.wood at gmail.com> wrote:
>>>>
>>>>> Hi
>>>>>
>>>>> It's my first post, so first of all, thanks to everyone who works on
>>>>> parenscript.  It is a lifesaver.
>>>>>
>>>>> If I 'use strict'; in the start of my scripts, they fail in some of
>>>>> the loops because parenscript is generating a 'with' (which is not allowed
>>>>> in strict mode).   There is quite a nice explanation of why it isn't
>>>>> allowed here:
>>>>>
>>>>>
>>>>> https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Functions_and_function_scope/Strict_mode
>>>>>
>>>>> It is easy to change my code to use 'do' instead, and for now, that's
>>>>> what I've done, but it's a shame not to be able to use parenscript's very
>>>>> nice loop for writing javascript loops.  I haven't looked at parenscript's
>>>>> code before this evening, but I think this is the relevant spot (in
>>>>> src/special-operators.lisp)
>>>>>
>>>>> (defun compile-loop-body
>>>>> ...
>>>>> (aif (sort (remove-duplicates *loop-scope-lexicals-captured*)
>>>>>                #'string< :key #'symbol-name)
>>>>>          `(ps-js:block
>>>>>               (ps-js:with
>>>>>                   ,(compile-expression
>>>>>                     `(create
>>>>>                       ,@(loop for x in it
>>>>>                               collect x
>>>>>                               collect (when (member x loop-vars) x))))
>>>>>                 ,compiled-body))
>>>>>          compiled-body)))
>>>>>
>>>>> Here is an example of some lisp and the js which it generates:
>>>>>
>>>>> (ps:ps (defun foo ()
>>>>>                   (loop for i from 1 to 5
>>>>>                      append (loop for j from 1 to 5
>>>>>                                  collect (list i j)))))
>>>>> ==>
>>>>> "function foo() {
>>>>>     return (function () {
>>>>>         var append9 = [];
>>>>>         for (var i = 1; i <= 5; i += 1) {
>>>>>             with ({ i : i }) {
>>>>>         ^^^^^^^^^^
>>>>>                 append9 = append9.concat((function () {
>>>>>                     var collect10 = [];
>>>>>                     for (var j = 1; j <= 5; j += 1) {
>>>>>                         collect10['push']([i, j]);
>>>>>                     };
>>>>>                     return collect10;
>>>>>                 })());
>>>>>             };
>>>>>         };
>>>>>         return append9;
>>>>>     })();
>>>>> };"
>>>>>
>>>>> What is the point of even having the 'with ({ i : i })' in there ??  I
>>>>> have tried removing the form starting (ps-js:with ... ) and the code which
>>>>> is then generated runs fine and has no 'with', but of course it is probably
>>>>> breaking something else.  I don't understand why it's there.
>>>>>
>>>>> Regards,
>>>>> Peter
>>>>>
>>>>> _______________________________________________
>>>>> 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
>>
>>
>
> _______________________________________________
> 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/20130323/9d7d40b2/attachment.html>


More information about the parenscript-devel mailing list