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

Peter Wood p.r.wood at gmail.com
Sat Mar 23 15:37:58 UTC 2013


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


More information about the parenscript-devel mailing list