I've made the following changes to PS LOOP and pushed them on a branch called "loop" pending the fixing of the pretty-printer as we discussed a couple weeks ago.
These changes capture some idioms that were common in our code. They deviate from CL's LOOP (I could not bring myself to implement "BEING THE HASH-KEYS OF") in ways that are appropriate for JS semantics. Since PS LOOP was not fully compatible with CL's LOOP anyway, this is not much of a loss.
(1) Added a FOR..OF clause that iterates through the keys of a JS object. This translates to JS's "for k in obj" loops and corresponds roughly to "loop for k being the hash-keys of" in CL.
(ps (loop :for k :of obj :do (foo k)))
"for (var k in obj) { foo(k); };"
To bind both key and value, you specify a pair. (This is an icky bit of syntax but it feels like the least icky option of the ones I'm aware of.)
(ps (loop :for (k v) :of obj :do (foo k v)))
"for (var k in obj) { var v = obj[k]; foo(k, v); };"
Destructuring works on the value position just like they do everywhere else, so you can unpack an array. (Note that in this example the key variable isn't needed so we can put NIL in there.)
(ps (loop :for (nil (v1 v2)) :of obj :do (foo v1 v2)))
"for (var _js23095 in obj) { var _js23096 = obj[_js23095]; var v1 = _js23096[0]; var v2 = _js23096[1]; foo(v1, v2); };"
... or named properties (by using keywords):
(ps (loop :for (nil (:hello :world)) :of obj :do (foo hello world)))
"for (var _js23097 in obj) { var var23099 = obj[_js23097]; var hello23101 = var23099.hello; var world23102 = var23099.world; foo(hello23101, world23102); };"
Side note: To get the scoping right for parallel loop clauses is tricky because LOOP orders things "step test step test" where JS's FOR does "step step test test", meaning that the second step will be evaluated even if the first test fails. It got trickier with the introduction of FOR..OF, so I adopted a simpler implementation that has some advantages (much tighter generated loop code) but also some disadvantages: (a) it was a pain to support :INITIALLY clauses so I dropped them (I'd be surprised if anyone ever used it), and (b) complex loops sometimes require an extra temporary variable to track whether the loop is on its first iteration or not.
(2) There is now a MAP..TO clause that works like COLLECT only instead of building an array, builds an object mapping keys to values:
(ps (loop :for str :in strs :map str :to (length str)))
"(function () { var _js23109 = strs.length; var map23110 = { }; for (var _js23108 = 0; _js23108 < _js23109; _js23108 += 1) { var str = strs[_js23108]; map23110[str] = str.length; }; return map23110; })();"
(3) In FOR..ON, a BY term can now be an integer. For example, you can say "by 2" instead of "by #'cddr" which makes little sense for JS arrays:
(ps (loop :for (key val) :on pairs :by 2 :do (foo key val)))
"for (var _js23094 = pairs; _js23094.length > 0; _js23094 = _js23094.slice(2)) { var key = _js23094[0]; var val = _js23094[1]; foo(key, val); };"
If BY doesn't get a number, it assumes it got a function:
(ps (loop :for (key val) :on pairs :by blah :do (foo key val)))
"for (var _js23112 = pairs; _js23112.length > 0; _js23112 = blah(_js23112)) { var key = _js23112[0]; var val = _js23112[1]; foo(key, val); };"
Daniel
I really like these extensions. I read somewhere (maybe CLtL?) that LOOP was supposed to have extension facilities. The original MIT LOOP macro (http://code.google.com/p/mcl/source/browse/library/mit-loop.lisp?r=0c886fce5...) which is still used in a lot of implementations has facilities for extension, and CLSQL comes with code that extends the loop macro in several CL implementations (http://items.sjbach.com/loop-extension.lisp).
All this to say that I think these changes would be great in Common Lisp itself (even the MAP...TO, anything to make hash tables easier to use in CL). You should post this to the lisp-pro mailing list (http://lists.common-lisp.net/cgi-bin/mailman/listinfo/pro) as a proposed CDR extension.
Vladimir
On Wed, Apr 11, 2012 at 8:05 PM, Daniel Gackle danielgackle@gmail.com wrote:
I've made the following changes to PS LOOP and pushed them on a branch called "loop" pending the fixing of the pretty-printer as we discussed a couple weeks ago.
These changes capture some idioms that were common in our code. They deviate from CL's LOOP (I could not bring myself to implement "BEING THE HASH-KEYS OF") in ways that are appropriate for JS semantics. Since PS LOOP was not fully compatible with CL's LOOP anyway, this is not much of a loss.
(1) Added a FOR..OF clause that iterates through the keys of a JS object. This translates to JS's "for k in obj" loops and corresponds roughly to "loop for k being the hash-keys of" in CL.
(ps (loop :for k :of obj :do (foo k)))
"for (var k in obj) { foo(k); };"
To bind both key and value, you specify a pair. (This is an icky bit of syntax but it feels like the least icky option of the ones I'm aware of.)
(ps (loop :for (k v) :of obj :do (foo k v)))
"for (var k in obj) { var v = obj[k]; foo(k, v); };"
Destructuring works on the value position just like they do everywhere else, so you can unpack an array. (Note that in this example the key variable isn't needed so we can put NIL in there.)
(ps (loop :for (nil (v1 v2)) :of obj :do (foo v1 v2)))
"for (var _js23095 in obj) { var _js23096 = obj[_js23095]; var v1 = _js23096[0]; var v2 = _js23096[1]; foo(v1, v2); };"
... or named properties (by using keywords):
(ps (loop :for (nil (:hello :world)) :of obj :do (foo hello world)))
"for (var _js23097 in obj) { var var23099 = obj[_js23097]; var hello23101 = var23099.hello; var world23102 = var23099.world; foo(hello23101, world23102); };"
Side note: To get the scoping right for parallel loop clauses is tricky because LOOP orders things "step test step test" where JS's FOR does "step step test test", meaning that the second step will be evaluated even if the first test fails. It got trickier with the introduction of FOR..OF, so I adopted a simpler implementation that has some advantages (much tighter generated loop code) but also some disadvantages: (a) it was a pain to support :INITIALLY clauses so I dropped them (I'd be surprised if anyone ever used it), and (b) complex loops sometimes require an extra temporary variable to track whether the loop is on its first iteration or not.
(2) There is now a MAP..TO clause that works like COLLECT only instead of building an array, builds an object mapping keys to values:
(ps (loop :for str :in strs :map str :to (length str)))
"(function () { var _js23109 = strs.length; var map23110 = { }; for (var _js23108 = 0; _js23108 < _js23109; _js23108 += 1) { var str = strs[_js23108]; map23110[str] = str.length; }; return map23110; })();"
(3) In FOR..ON, a BY term can now be an integer. For example, you can say "by 2" instead of "by #'cddr" which makes little sense for JS arrays:
(ps (loop :for (key val) :on pairs :by 2 :do (foo key val)))
"for (var _js23094 = pairs; _js23094.length > 0; _js23094 = _js23094.slice(2)) { var key = _js23094[0]; var val = _js23094[1]; foo(key, val); };"
If BY doesn't get a number, it assumes it got a function:
(ps (loop :for (key val) :on pairs :by blah :do (foo key val)))
"for (var _js23112 = pairs; _js23112.length > 0; _js23112 = blah(_js23112)) { var key = _js23112[0]; var val = _js23112[1]; foo(key, val); };"
Daniel
parenscript-devel mailing list parenscript-devel@common-lisp.net http://lists.common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
parenscript-devel@common-lisp.net