On Jan 14, 2012, at 9:12 AM, David Herman wrote:
> On Jan 13, 2012, at 9:04 PM, Axel Rauschmayer wrote:
>
>> I think it’s a valid concern. The idea is: If I can implement my own loops
>> (the nice-looking paren-free syntax feeds that illusion!) then I also want
>> those loops to have break and continue. You could statically determine what
>> construct, say, a break applies to and either throw a BreakException (if it
>> applies to a lambda) or TCP-break (if it applies to an enclosing non-lambda
>> loop). In the examples below, when I see a continue, I look for the
>> innermost enclosing loop braces and the ones belong to list[i].forEach are
>> definitely candidates.
I also think there is a valid concern here. The issue isn't whether the
current break/continue statement semantics can be preserved when encapsulated
by a block lambda. That is clearly possible because the semantics are tied to
specific syntactically identifiable statements. The real issue is whether the
same syntax can support break/continue-like semantics for user defined controls
abstractions built using Block Lambdas. Put another way, can Block Lambdas
based abstractions completely replace (at least conceptually) the built-in loop
statements. This would seem desirable, but the answer isn't obviously yes --
hence the concern.
Consider the following function:
function forNum(start, end,increment=1,body={||}) {
if (start > end) return;
body(start);
return forNum(start+increment,end,increment,body);
}
User of the looping abstraction might reasonably expect to say something like:
forNum(first,last,1) {|n|
if (n&1) continue;
if (n==special) {
doSpecialStuff(n);
break;
}
doEven(n);
};
with the meaning being essentially the same as they would get if they had used
a for statement instead of the forNum function. Similarly for:
forNum(first,last,1) {|x|
forNum(firstY(x),lastY(x),1) {|n|
if (n&1) continue;
if (n==special) {
doSpecialStuff(n);
break;
}
doEven(n);
}
};
where the continue and break apply to the inner loop rather than the outer
loop. Before trying to figure out how to we could make these work (something
like exceptions seems like a reasonable path) we should for look at some other
possible use cases. Consider:
function logBlk(label, block) {
logStream.put("starting "+label +Date());
block();
logStream.put("completed "+label +Date());
}
used in:
forNum(first,last,1) {|n|
logBlk("process even values") {||
if (n&1) continue;
if (n==special) {
doSpecialStuff(n);
break;
}
doEven(n);
}
};
This later use of forNum has essentially the same shape (think about them with
forNum and logBlk renamed to func1 and func2) as the doubly nested example, yet
the "binding" of the continue and break affects need to be different. How can
this be achieved? Apparently there needs to be something additional in the
implementation of forNum that won't occur in logBlk. Also, from a usage
perspective, how does the user of these functions know that logBlk is
transparent to break/continue while forNum is not? Presumably, this needs to
be part of the usage "contract" of those functions. How can that be expressed?
Is it implicit or explicit?
I'm not aware of very many languages that have addressed these issues,
particularly in combination with syntactic looping constructs. The problem
doesn't exist in Smalltalk because it doesn't have either break/continue or
syntactic loops. It sounds like Ruby's solution may not be quite right. The
most serious attempt that I know of to address this issue is in the BGGA
closure proposal for Java: http://www.javac.info/closures-v05.html (see Loop
Abstractions section),
http://gafter.blogspot.com/2006/10/iterative-control-abstraction-user.html
http://tronicek.blogspot.com/2008/08/nonlocal-transfer.html
http://tronicek.blogspot.com/2008/08/modifier-for.html
>
> If I understand your suggestion, you're proposing that non-local break and
> continue should be exposed as standard exceptions, and then implementors of
> loop-like abstractions could choose to catch them. E.g. you could implement
> forEach as:
>
> Array.prototype.forEach = function(f) {
> for (let i = 0, n = this.length; i < n; i++) {
> try {
> f.call(this, this[i], i);
> } catch (e) {
> if (e instanceof BreakException)
> break;
> else if (e instanceof ContinueException)
> continue;
> else
> throw e;
> }
> }
> };
>
> Whereas a function that does *not* want to expose whether it's using loops
> would simply do nothing with BreakException and ContinueException, and they
> would propagate out and you'd get the lexical scoping semantics. Meanwhile,
> break/continue with an explicit target would never be catch-able.
>
> Did I understand your suggestion correctly?
>
> This *may* not violate TCP (I'm not quite sure), but I'm not enthusiastic
> about the idea. The semantics is significantly more complicated, and it
> requires you to understand whether a higher-order function like forEach is
> catching these exceptions or not. So it becomes an additional part of the API
> of a function. If someone doesn't document what they do with BreakException
> and ContinueException, then writing callbacks you won't actually be able to
> predict what `break` and `continue` will do.
>
> Dave
>
> _______________________________________________
> es-discuss mailing list
> [email protected]
> https://mail.mozilla.org/listinfo/es-discuss
>
_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss