With more and more discussion about closures and non-local flow control and various stack-manipulating events becoming part and parcel on the JVM, it seems like we need to introduce a new exception type.
There's two reasons for this:
- catch (Throwable t) or catch (Error e) would break current non-local flow control mechanisms based on exceptions or errors - some of you whiners don't like us using exceptions or errors for non-local flow control
So, what about a new type of exception-like feature: Jump
public class Jump {}
public class NonLocalReturn < Jump { Object returnValue;
}
Jumps would not be catchable as either errors or throwables, though they would still trigger finally blocks. Because they'd be a new class of exception-like feature, no existing code would be incorrectly trying to catch them. And they're explicitly stated to be used for these sorts of "jump" events.
> With more and more discussion about closures and non-local flow control > and various stack-manipulating events becoming part and parcel on the > JVM, it seems like we need to introduce a new exception type.
> There's two reasons for this:
> - catch (Throwable t) or catch (Error e) would break current non-local > flow control mechanisms based on exceptions or errors > - some of you whiners don't like us using exceptions or errors for > non-local flow control
> So, what about a new type of exception-like feature: Jump
> public class Jump {}
> public class NonLocalReturn < Jump { > Object returnValue; > }
> Jumps would not be catchable as either errors or throwables, though they > would still trigger finally blocks. Because they'd be a new class of > exception-like feature, no existing code would be incorrectly trying to > catch them. And they're explicitly stated to be used for these sorts of > "jump" events.
On 10/20/07, Charles Oliver Nutter <charles.nut...@sun.com> wrote:
> So, what about a new type of exception-like feature: Jump
> public class Jump {}
> public class NonLocalReturn < Jump { > Object returnValue; > }
> Jumps would not be catchable as either errors or throwables, though they > would still trigger finally blocks. Because they'd be a new class of > exception-like feature, no existing code would be incorrectly trying to > catch them. And they're explicitly stated to be used for these sorts of > "jump" events.
This sounds like a great idea. It uses existing structures in a new way that could then be optimized for the needs of non-java languages at the VM level. I can't see a downside.
1) Where does it jump to? The next line after the method invocation? Or a finally block, if there is on? What if I haven't defined a finally block in the enclosing (method-calling) scope? Does it bubble up to some other finally in a broader scope? What if I'm starting with a script and haven't defined any finally at all? I guess the compiler could detect that a Jump was being declared (how?) and force you to include a finally.
2) The word "jump" seems to imply a location (jump-to, jump-back-to) but no destination, specifically.
I'm probably missing the larger discussion here, though I've been following some of the talk on closures. Would be interested to hear more.
Patrick Wright wrote: > The idea is interesting, but, two points
Good points...
> 1) Where does it jump to? The next line after the method invocation? > Or a finally block, if there is on? What if I haven't defined a > finally block in the enclosing (method-calling) scope? Does it bubble > up to some other finally in a broader scope? What if I'm starting with > a script and haven't defined any finally at all? I guess the compiler > could detect that a Jump was being declared (how?) and force you to > include a finally.
The typical way a non-local return (for example) is implemented now is by constructing the closure containing it such that it has a unique ID to return to. NonLocalReturn would then be constructed with this ID, and the jump would bubble back out through the stack until someone interested in NonLocalReturn events caught it and saw it had the appropriate id, at which point the return value would be unwrapped and bubbling would stop.
So it is still more of a "throw" or "raise" or "trigger event", but the intention is that somewhere, someone will be interested in being the "landing point" for the jump.
Now this is just the brute-force way we've been implementing it. It could certainly be what the actual JDK7 generates as bytecode, ideally all behind the scenes when you do a return within a closure. However I'd wager there are specific optimizations that could be done to simplify it. For example, exception tables could be generated such that they're only interested in the jumps generated in that scope, both types and IDs, allowing other jumps to transparently pass through without the cost of catching and re-throwing.
Neil Gafter: I recall you advocating non-local returns, but I don't remember if you also advocate other types of non-local flow control like break or continue. Do you?
myList.each {(String element) if (element.equals("foo")) break; }
> 2) The word "jump" seems to imply a location (jump-to, jump-back-to) > but no destination, specifically.
Perhaps it's more like "JumpAndHopeSomeoneWillCatchYouOnTheWayDown". I think Jump is a bit nicer though.
The accurate name would probably be "BeginStackEscape" or something similar.
Neal Gafter wrote: > I like this. The exception tables presumably would be allowed to contain > entries for subtypes of Jump.
And if possible, optimized to only be interested in jumps generated immediately within the interesting scope, to filter out jumps generated at deeper scopes not directly of interest.
/** runs the given closure five times for each element */ public void fiveForEach(Closure c) { int elementIndex = 0;
while (elementIndex < size) { int loopCount = 0;
loop { // loop takes a closure if (loopCount >= 5) break; // return ends the inner loop
... public Object weirdFind() { x = createSomeList(); x.fiveForEach {(Object o) if (someTest(o)) return o; }
}
Here, the run method is only interested in a ReturnJump, and more specifically, it's only interested in the ReturnJump that would be generated within the closure passed to fiveForEach. The loop inside fiveForEach is only interested in the BreakJump generated inside the loop. However, because the ReturnJump is generated by a call to that closure inside the loop, it will pass through the loop's jump/exception-handling table. Rather than have all closure-accepting methods catch and filter all jumps, it would be preferable that they only catch the ones they could reasonably respond to.
Of course, it should still be possible for someone to write code that explicitly catches Jump, to avoid a rogue (i.e. badly compiled?) jump event from blowing the whole stack. This could also be a recommended documentation practice:
will capture any return or break jumps passing through it: /** * ... * @jumps ReturnJump, BreakJump */
...
will capture all jumps; don't expect jumps passing through this to escape /** * ... * @jumps *
> With more and more discussion about closures and non-local flow control > and various stack-manipulating events becoming part and parcel on the > JVM, it seems like we need to introduce a new exception type.
> There's two reasons for this:
> - catch (Throwable t) or catch (Error e) would break current non-local > flow control mechanisms based on exceptions or errors > - some of you whiners don't like us using exceptions or errors for > non-local flow control
> So, what about a new type of exception-like feature: Jump
> public class Jump {}
> public class NonLocalReturn < Jump { > Object returnValue; > }
> Jumps would not be catchable as either errors or throwables, though they > would still trigger finally blocks. Because they'd be a new class of > exception-like feature, no existing code would be incorrectly trying to > catch them. And they're explicitly stated to be used for these sorts of > "jump" events.
On 10/20/07, Charles Oliver Nutter <charles.nut...@sun.com> wrote:
> Neil Gafter: I recall you advocating non-local returns, but I don't > remember if you also advocate other types of non-local flow control like > break or continue. Do you?
Yes, but the way it is specified in http://www.javac.info, you'd want to declare and invoke the "each" method with the "for" qualifier, otherwise it isn't a matching target for the break. The invocation syntax would then be
for myList.each(String element :) { if (element.equals("foo")) break;
}
for a member method, or
for each(String element: myList) { if (element.equals("foo")) break;
}
for a static method. I'm working separately on a spec/implementation of extension methods, which roughly speaking would allow you to use these forms interchangeably. In other words, to take advantage of this you wouldn't have to make changes to the Collection interface (interface changes break backward compatibility in Java).
In this work your doing are there any provisions for determining the current position in the loop? i.e. is there anything coming after me?
An example from some code I was just fixing up was taking a list of class names and combining them into a ; delimited string (and erroneously appending a trailing separator), without knowing the current position, or if something follows you one has to manually keep track of position to optionally append a ; rather than just call say 'iterator.hasNext()" inside the loop.
With the syntax below, it would be nice if a new implicit variable was available which gave up such info, maybe something like:
StringBuilder sb = new StringBuilder(); for each(String className: myListOfClassNames) { sb.append(className); if (each.hasNext()) sb.append(";");
}
with each being a new implicit variable of some mythical Iteration interface (this might also provide a getIndex() method to identify which iteration of the loop were in).
Having access to this information is one of the things I miss with the new enhanced for loops...
On 10/21/07, Neal Gafter <neal.gaf...@gmail.com> wrote:
> for each(String element: myList) { > if (element.equals ("foo")) break; > }
> for a static method. I'm working separately on a spec/implementation of > extension methods, which roughly speaking would allow you to use these forms > interchangeably. In other words, to take advantage of this you wouldn't have > to make changes to the Collection interface (interface changes break > backward compatibility in Java).
-- It`s not the tree that forsakes the flower, but the flower that forsakes the tree Someday I`ll learn to love these scars - Bye Bye Beautiful
Is it really necessary to have a new feature. I would of thought a convention that you don't catch something derived from Jump was sufficient. Much like you are highly advised not to catch something derived from Error at the moment. I say this because inner classes give sufficient power to make non-local jumps fast at present, e.g.:
class MyList<E> { <R> MyList<R> map( Method<R, E> f ) { ... } // rest
}
// Translated from a better syntax in to standard Java Integer example( MyList<Integer> list ) { class ExampleReturn extends NonLocalReturn { static final ExampleReturn instance = new ExampleReturn(); // Pre- made singleton } try { ... list.map( new Method1<Integer, Integer>() { public Integer call( Integer x ) { ... throw ExampleReturn.instance; // fast - pre made } } ); ... } catch ( ExampleReturn e ) { return (Integer) e.getValue(); }
On 10/20/07, Mark Derricutt <m...@talios.com> wrote:
> In this work your doing are there any provisions for determining the > current position in the loop? i.e. is there anything coming after me?
> An example from some code I was just fixing up was taking a list of class > names and combining them into a ; delimited string (and erroneously > appending a trailing separator), without knowing the current position, or if > something follows you one has to manually keep track of position to > optionally append a ; rather than just call say ' iterator.hasNext()" > inside the loop.
> With the syntax below, it would be nice if a new implicit variable was > available which gave up such info...
These loops are library methods, not separate looping language extensions. Closures just make it possible for programmers to define them. One can easily make a looping method that has an index or that exposes an iterator.
On 10/20/07, hlovatt <howard.lov...@gmail.com> wrote:
> Is it really necessary to have a new feature. I would of thought a > convention that you don't catch something derived from Jump was > sufficient.
A new exception hierarchy outside Throwable is necessary for closures to satisfy Tennent's Correspondence Principle, which as you know is a powerful litmus test for the expressive power of a language feature such as closures.
Alex Tkachman wrote: > If I understand correctly for jumps we can get rid of filling stack > trace, so it will be more or less effective.
That's how our "Jump" type in JRuby works: we override fillInStackTrace to do nothing. In cases where there's no mutable data on the jump, we also use a single object instance everywhere.
hlovatt wrote: > Is it really necessary to have a new feature. I would of thought a > convention that you don't catch something derived from Jump was > sufficient. Much like you are highly advised not to catch something > derived from Error at the moment. I say this because inner classes > give sufficient power to make non-local jumps fast at present, e.g.:
The problem is that there's already code out there that catches Exception or Throwable, which will break any language implementation using RuntimeException subclasses to do non-local flow control. Scala opted to go with Error subclasses, but even here there's the possibility someone could be catching Error for other reasons. The fact that Jump would be new and that there would be no other valid reason for catching it other than intercepting non-local flow control is exactly the point. We're not overloading an existing feature in ways incompatible with current usage patterns. We're accepting that non-local flow control is a fact of life for many language implementations, and that a first-class construct is needed.
On Oct 21, 12:40 pm, "Neal Gafter" <neal.gaf...@gmail.com> wrote:
> A new exception hierarchy outside Throwable is necessary for closures to > satisfy Tennent's Correspondence Principle, which as you know is a powerful > litmus test for the expressive power of a language feature such as closures.
Tennent used his Principle of Correspondence to argue against return, break, and continue and instead advocated that every block should have one exit point (end of block). His argument in Javanese is:
// Start with this block - the block is between the braces int i = 0; while ( true ) { if ( i > 3 ) break; out.println( i );
}
Then wrap the block in another block:
// Using Correspondence it should be the same - but it isn't int i = 0; while ( true ) { while ( true ) { if ( i > 3 ) break; out.println( i ); }
}
Oops meaning of block changed, contrast this with:
int i = 0; while ( true ) { if ( i <= 3 ) out.println( i );
}
Then wrap the block in another loop - the block now has the same result:
int i = 0; while ( true ) { while ( true ) { if ( i <= 3 ) out.println( i ); }
}
Therefore using Correspondence as an argument for a JVM extension to support return etc. from within closures is weak, since Tennent argued, using his own principle, that the correct answer is not to have return etc.
A compromise position is to allow return etc. but require them to name the block they are returning from (like break and continue currently do but also extend the naming to include method names). Then at least the action of the block is clearly flagged, e.g.:
int i = 0; iLoop: while ( true ) { while ( true ) { if ( i > 3 ) break iLoop; // still works as expected out.println( i ); }
}
Having said that the block must be named, then the standard exception mechanism can be used and this has the added advantage of providing a compile time check for asynchronous use cases, e.g.:
// in the example below method { .. } is suggested short syntax for an inner class (closure) int i = 0; iLoop: while ( true ) { invokeLater( method { ...; break iLoop; ... } ); // Error since invokeLater doesn't catch iLoopBreakException }
}
The above example is caught at compile time because when expanded the object passed to invokeLater throws a checked exception, iLoopBreak, which isn't caught (and can't be caught because it is a local class) by invokeLater. See previous return example for how break etc. are expanded.
I would also caution that adding support for non-local returns will probably not be worth the trouble, since I doubt that non-local returns will be used much. For example, what is a good use case (remember that many languages don't have return etc. at all)? A second question, how often do you use a named break or continue? You can always add features but a more pertinent question is whether a feature is worth adding, not whether it is technically feasible to add. There is a ledger here, you need to account cost of addition against usefulness.
NOTE: There is a typo in my original post, exception Jump is meant to extend Exception not RuntimeException, IE it is meant to be a checked exception so that asynchronous use cases are caught at compile time. Also note that the exception, ExampleReturn, is an inner class and therefore private to the method, example, in which the inner class (closure) is declared in and therefore cannot be caught by anything else (except for catching Jump which like Error it is suggested that you don't do).
On Oct 22, 10:44 am, Charles Oliver Nutter <charles.nut...@sun.com> wrote:
> The problem is that there's already code out there that catches > Exception or Throwable, which will break any language implementation > using RuntimeException subclasses to do non-local flow control. Scala > opted to go with Error subclasses, but even here there's the possibility > someone could be catching Error for other reasons. The fact that Jump > would be new and that there would be no other valid reason for catching > it other than intercepting non-local flow control is exactly the point. > We're not overloading an existing feature in ways incompatible with > current usage patterns. We're accepting that non-local flow control is a > fact of life for many language implementations, and that a first-class > construct is needed.
OK these are valid points. So why not make a new subclass of Throwable called Jump that acts like a checked exception, Exception, in Java and recommend that people don't catch it (but importantly allow them to catch it - see below). I doubt that any mechanism is totally bullet proof, so I don't see a problem with a simple recommendation in the Java context. For other languages it may be appropriate to enforce that Jump cannot be caught in that language. I am deliberately putting forward a Java and a non-Java viewpoint since in the ideal world multiple languages can co-exist and call each other and therefore control flow needs to have identical semantics in all cases. If it is a specific non-Java feature then a method that uses it cannot be called from Java, if on the other hand it is like a checked exception then Java can call it (and catch or throw Jump if that is what it takes to deal with the non-Java method).
On 10/21/07, hlovatt <howard.lov...@gmail.com> wrote:
> Therefore using Correspondence as an argument for a JVM extension to > support return etc. from within closures is weak, since Tennent > argued, using his own principle, that the correct answer is not to > have return etc.
I don't think removing return from the Java Programming Language is an option. It is best to introduce as few new violations of the principle as possible. Two wrongs don't make a right.
A compromise position is to allow return etc. but require them to name
> the block they are returning from (like break and continue currently > do but also extend the naming to include method names). Then at least > the action of the block is clearly flagged, e.g.:
We are already looking at allowing labelled closure returns in the Java Closures spec (and have a draft spec for it). Nevertheless, as a separate issue exceptions outside Throwable are still needed.
hlovatt wrote: > OK these are valid points. So why not make a new subclass of Throwable > called Jump that acts like a checked exception, Exception, in Java and > recommend that people don't catch it (but importantly allow them to > catch it - see below). I doubt that any mechanism is totally bullet > proof, so I don't see a problem with a simple recommendation in the > Java context. For other languages it may be appropriate to enforce > that Jump cannot be caught in that language. I am deliberately putting > forward a Java and a non-Java viewpoint since in the ideal world > multiple languages can co-exist and call each other and therefore > control flow needs to have identical semantics in all cases. If it is > a specific non-Java feature then a method that uses it cannot be > called from Java, if on the other hand it is like a checked exception > then Java can call it (and catch or throw Jump if that is what it > takes to deal with the non-Java method).
Full disclosure: I'm rapidly losing respect for checked exceptions.
That said, I think making Jump a checked exception is a *terrible* way to go. Not only would it require adding the checked exception to every single API we want to closure-enable, it requires that anywhere we want to have non-local flow control in play we have to explicitly declare it. Even worse, it requires that all intermediate APIs that may not even care about non-local flow control would also have to declare they throw jumps even if they're just passing a closure on to a deeper method.
// foo does the actual call... void foo(Closure c1) { c1.call();
}
// ...but bar, simply by virtue of accepting a closure, would have to // declare that it throws Jump void bar(Closure c2) { foo(c2);
}
Essentially, it would require that all methods dealing with closures must throw Jump, even if the closures they're passed never use non-local flow control, simply because *they might*.
I think the detail you're missing here is that non-local flow control is not a feature of the closure-friendly API you are calling, it's a feature of the closure you're passing to it. Therefore it makes no sense to require that all closure-accepting methods throw Jump, since in many (most?) cases they won't, nor will they typically care if non-local Jumps pass through them.
Now all I need to do is to convince you to base your closures on normal inner classes <- this comment is meant in jest, it isn't a dig at you
> Nevertheless, as a separate > issue exceptions outside Throwable are still needed.
I agree that there are some advantages, but question whether it is really worth the trouble (i.e. the cost penalty ratio doesn't seem that good to me).
On Oct 22, 11:49 am, Charles Oliver Nutter <charles.nut...@sun.com> wrote:
> Essentially, it would require that all methods dealing with closures > must throw Jump, even if the closures they're passed never use non-local > flow control, simply because *they might*.
> I think the detail you're missing here is that non-local flow control is > not a feature of the closure-friendly API you are calling, it's a > feature of the closure you're passing to it. Therefore it makes no sense > to require that all closure-accepting methods throw Jump, since in many > (most?) cases they won't, nor will they typically care if non-local > Jumps pass through them.
Maybe, you could choose to make Jump exceptions non-checked. But as a counter example consider multi-threaded code or event code where it makes no sense to have a non-local Jump. In these cases you want the compiler to tell you that you have made a mistake. It is really a matter of what you consider most important. I write quite a bit of event and multi-threaded code and therefore like catching this error.
My proposal for named non-local jumps, http://www.artima.com/weblogs/viewpost.jsp?thread=182412, did include a mechanism for dealing with both cases simply. The proposal also extends Java to accept a list, possibly empty, of exceptions that can be thrown as a generic-type-var-arg. Thus if an inner class (closure) threw no exceptions then it could be passed to a method that didn't deal with exceptions, conversely if it threw a checked exception, Jump in this context, then a method it was passed to would need to deal with that exception or declare the exception itself.
The main point is that we don't really need to extend the JVM, the current exception mechanism seems sufficient.
On 10/21/07, hlovatt <howard.lov...@gmail.com> wrote:
> I agree that there are some advantages, but question whether it is > really worth the trouble (i.e. the cost penalty ratio doesn't seem > that good to me).
The costs are ones that only those of us who implement languages pay. The benefits are accrued by all those who use the language. Seems like a pretty good tradeoff for our users.
this is a very good idea and I can see no downsides. Those exception types could also automatically be setup to not create a stack trace (which saves a lot of overhead). I've been using Error exception for this myself (seems Scala does too), but a dedicated type that nobody is catching would be a good improvement.
Geert
On 20 Oct 2007, at 06:59, Charles Oliver Nutter wrote:
> With more and more discussion about closures and non-local flow > control > and various stack-manipulating events becoming part and parcel on the > JVM, it seems like we need to introduce a new exception type.
> There's two reasons for this:
> - catch (Throwable t) or catch (Error e) would break current non-local > flow control mechanisms based on exceptions or errors > - some of you whiners don't like us using exceptions or errors for > non-local flow control
> So, what about a new type of exception-like feature: Jump
> public class Jump {}
> public class NonLocalReturn < Jump { > Object returnValue; > }
> Jumps would not be catchable as either errors or throwables, though > they > would still trigger finally blocks. Because they'd be a new class of > exception-like feature, no existing code would be incorrectly > trying to > catch them. And they're explicitly stated to be used for these > sorts of > "jump" events.
> On Oct 21, 12:40 pm, "Neal Gafter" <neal.gaf...@gmail.com> wrote: >> A new exception hierarchy outside Throwable is necessary for closures to >> satisfy Tennent's Correspondence Principle, which as you know is a powerful >> litmus test for the expressive power of a language feature such as closures.
> Tennent used his Principle of Correspondence to argue against return, > break, and continue and instead advocated that every block should have > one exit point (end of block).
sorry for warming up this thread after a week... It is a shame that I not yet had a chance of reading this... but maybe you can answer some questions.
For example how did he define exit point? for example in this case
def foo() { throw new RuntimeException("something") }
while (true) { foo()
}
you said "end of block", so I guess it has only one exit point... or does the foo() method have two then? If yes, wouldn't mean that exceptions are bad in Tennent's Correspondence Principle?
But ok, let us say this has only one exit point.... Now, if we have a list and we want to proceed all elements until we meet a certain element and then stop processing, then I would write something like this:
for (XY element : list) { if (element==myBreakCondition) break doSometing(element)
}
this has (at last) two exit point because of the loop, so I guess I would have to change the loop condition to do the additional check
XY element for (Iterator it=list.iterator(); it.hasNext() && element!=myBreakCondition;) { element = it.next() doSomething(element)
where flowControl.breakIf would throw an exception, each (I will call each an iteration method, because it iterates over the list here) would catch that exception and use flowControl to check the exception... Would this still fulfill the "only one exit point per block" idea?
Or the next question... if we say that a "closure syntax" overlaps with the syntax generally used by blocks, but if the "closure" is no block... does the principle then still apply to break/continue which have to be applied to the iteration method?
Or let us say a break statement is not allowed in a "closure".. what does the principle say to this? I mean having one exit point per block is easily fulfilled with that, because it would not compile... Is the language to be considered as "less expressive" then, even if the break statement can be expressed by other elements that are not of syntactic nature? Or if I had a different syntactic element to do a break in a closure.. would the language then still be considered as "less powerful"?
> Having said that the block must be named, then the standard exception > mechanism can be used and this has the added advantage of providing a > compile time check for asynchronous use cases, e.g.:
> // in the example below method { .. } is suggested short syntax for an > inner class (closure) > int i = 0; > iLoop: while ( true ) { > invokeLater( method { ...; break iLoop; ... } ); // Error since > invokeLater doesn't catch iLoopBreakException > } > }
> The above example is caught at compile time because when expanded the > object passed to invokeLater throws a checked exception, iLoopBreak, > which isn't caught (and can't be caught because it is a local class) > by invokeLater. See previous return example for how break etc. are > expanded.
on the other hand.... why is that a illegal usage of "break iLoop"? I mean ok, invokeLater might execute the "closure" in a different Thread, but what if not?
I guess that's why the closure proposal expects the usage of "for" here:
l1: while (true) { l2: for each(element:list) { if (element == myBreakCondition) break l1 break l2 }
}
from a pragmatic point of view I guess you would have to tell your iteration method that it needs to respond to "break l2", but not to "break l1". But of course I can not write:
void for each(List list, MyClosure closure, Label label) { label: for (x : list) closure.call()
}
and you can get even more fun with something like:
l1: while (true) { l2: for each(element:list) { check (element == myBreakCondition, {break l1}) break l2 }
}
which means that check should execute the closure {break l1} if element==myBreakCondition. If we use the interface coercion that was proposed, then we would have to throw an compile time error here if the interface (for example Runnable) does not throw that exception. Or not? I mean according to your text above it must, because Runnable does not throw that exception and check would have to throw that exception too. But the advantage of the "for" keyword usage seems to be kind of lost here.
So from looking at this from the perspective of a static type system where I want to have compile time checks as much as possible a checked exception would make sense to me. From the perspective of a API writer I get horrified but that ideas, because it means any method that somehow has to handle "closures" containing a break/continue would have to throw the exception. It would be equally annoying to he RemoteException for example. On the bytecode level it makes no difference, because the JVM is not checking if a exception is checked or not, only the Java compiler does.
Regardless if new exception type is defined or not, I guess the API writers still get exposed to an implementation detail if they want to have their custom iteration methods which I think is bad...
And even if there is a way to express the each without letting it explicitly catch and evaluate the exception (I would really like to know how to get around that), a checked exception would still mean, that at some point any API writer gets exposed to this implementation detail when they use the iteration method on their own.
> I would also caution that adding support for non-local returns will > probably not be worth the trouble, since I doubt that non-local > returns will be used much. For example, what is a good use case > (remember that many languages don't have return etc. at all)?
so you have no problem with break, but with return? I hope my check method example above did show you that it is not only "return", it is also break/continue to some extend.
Tennent's book is quite old and talks about Pascal, I don't recall any specific discussion of exceptions and exceptions were not part of standard Pascal. So my guess is that any form of non-local jumps would not be covered by Tennent directly. The princiople says that the meaning of the code should not change if enclosed in a block, e.g. while loop, but it means any type of block and therefore also a try catch block.
So my reading is that any form of non-block structured jump is excluded by the principle, i.e. no return, break, continue, or throw statements. All these have to be simulated with a status variable. Interestingly if an inner class was used instead of a block then a control variable could be used and exceptions avoided all together:
enum Status { CONTINUE, BREAK; }
class Loop implements Method1< Void, Object > { Status status = CONTINUE; void breakLoop() { status = BREAK; }
}
class List { private Object[] data; ... void each( Loop l ) { for ( int i = 0; i < data.length && l.status == CONTINUE; i++ ) { l.call( data[ i ] ); } }
}
// My favourite syntax follows! list.each method( e ) { if ( someCondition ) { breakLoop() } };
On Oct 28, 9:38 am, Jochen Theodorou <blackd...@gmx.org> wrote:
> > On Oct 21, 12:40 pm, "Neal Gafter" <neal.gaf...@gmail.com> wrote: > >> A new exception hierarchy outside Throwable is necessary for closures to > >> satisfy Tennent's Correspondence Principle, which as you know is a powerful > >> litmus test for the expressive power of a language feature such as closures.
> > Tennent used his Principle of Correspondence to argue against return, > > break, and continue and instead advocated that every block should have > > one exit point (end of block).
> sorry for warming up this thread after a week... > It is a shame that I not yet had a chance of reading this... but maybe > you can answer some questions.
> For example how did he define exit point? for example in this case
> def foo() { throw new RuntimeException("something") }
> while (true) { > foo()
> }
> you said "end of block", so I guess it has only one exit point... or > does the foo() method have two then? If yes, wouldn't mean that > exceptions are bad in Tennent's Correspondence Principle?
> But ok, let us say this has only one exit point.... Now, if we have a > list and we want to proceed all elements until we meet a certain element > and then stop processing, then I would write something like this:
> for (XY element : list) { > if (element==myBreakCondition) break > doSometing(element)
> }
> this has (at last) two exit point because of the loop, so I guess I > would have to change the loop condition to do the additional check
> XY element > for (Iterator it=list.iterator(); > it.hasNext() && element!=myBreakCondition;) { > element = it.next() > doSomething(element)
> where flowControl.breakIf would throw an exception, each (I will call > each an iteration method, because it iterates over the list here) would > catch that exception and use flowControl to check the exception... Would > this still fulfill the "only one exit point per block" idea?
> Or the next question... if we say that a "closure syntax" overlaps with > the syntax generally used by blocks, but if the "closure" is no block... > does the principle then still apply to break/continue which have to be > applied to the iteration method?
> Or let us say a break statement is not allowed in a "closure".. what > does the principle say to this? I mean having one exit point per block > is easily fulfilled with that, because it would not compile... Is the > language to be considered as "less expressive" then, even if the break > statement can be expressed by other elements that are not of syntactic > nature? Or if I had a different syntactic element to do a break in a > closure.. would the language then still be considered as "less powerful"?
> [...]
> > Having said that the block must be named, then the standard exception > > mechanism can be used and this has the added advantage of providing a > > compile time check for asynchronous use cases, e.g.:
> > // in the example below method { .. } is suggested short syntax for an > > inner class (closure) > > int i = 0; > > iLoop: while ( true ) { > > invokeLater( method { ...; break iLoop; ... } ); // Error since > > invokeLater doesn't catch iLoopBreakException > > } > > }
> > The above example is caught at compile time because when expanded the > > object passed to invokeLater throws a checked exception, iLoopBreak, > > which isn't caught (and can't be caught because it is a local class) > > by invokeLater. See previous return example for how break etc. are > > expanded.
> on the other hand.... why is that a illegal usage of "break iLoop"? I > mean ok, invokeLater might execute the "closure" in a different Thread, > but what if not?
> I guess that's why the closure proposal expects the usage of "for" here:
> l1: while (true) { > l2: for each(element:list) { > if (element == myBreakCondition) break l1 > break l2 > }
> }
> from a pragmatic point of view I guess you would have to tell your > iteration method that it needs to respond to "break l2", but not to > "break l1". But of course I can not write:
> void for each(List list, MyClosure closure, Label label) { > label: for (x : list) closure.call()
> }
> and you can get even more fun with something like:
> which means that check should execute the closure {break l1} if > element==myBreakCondition. If we use the interface coercion that was > proposed, then we would have to throw an compile time error here if the > interface (for example Runnable) does not throw that exception. Or not? > I mean according to your text above it must, because Runnable does not > throw that exception and check would have to throw that exception too. > But the advantage of the "for" keyword usage seems to be kind of lost here.
> So from looking at this from the perspective of a static type system > where I want to have compile time checks as much as possible a checked > exception would make sense to me. From the perspective of a API writer I > get horrified but that ideas, because it means any method that somehow > has to handle "closures" containing a break/continue would have to throw > the exception. It would be equally annoying to he RemoteException for > example. On the bytecode level it makes no difference, because the JVM > is not checking if a exception is checked or not, only the Java compiler > does.
> Regardless if new exception type is defined or not, I guess the API > writers still get exposed to an implementation detail if they want to > have their custom iteration methods which I think is bad...
> And even if there is a way to express the each without letting it > explicitly catch and evaluate the exception (I would really like to know > how to get around that), a checked exception would still mean, that at > some point any API writer gets exposed to this implementation detail > when they use the iteration method on their own.
> > I would also caution that adding support for non-local returns will > > probably not be worth the trouble, since I doubt that non-local > > returns will be used much. For example, what is a good use case > > (remember that many languages don't have return etc. at all)?
> so you have no problem with break, but with return? I hope my check > method example above did show you that it is not only "return", it is > also break/continue to some extend.
> Tennent's book is quite old and talks about Pascal, I don't recall any > specific discussion of exceptions and exceptions were not part of > standard Pascal. So my guess is that any form of non-local jumps would > not be covered by Tennent directly. The princiople says that the > meaning of the code should not change if enclosed in a block, e.g. > while loop, but it means any type of block and therefore also a try > catch block.
I found out it is from around 1977, another reason why it is so hard to find... citeseer doesn't have papers that old ;) But I also think that an exception would be counted as exit point. On the other hand simulating an exception by control variables is not very nice.
> So my reading is that any form of non-block structured jump is > excluded by the principle, i.e. no return, break, continue, or throw > statements. All these have to be simulated with a status variable. > Interestingly if an inner class was used instead of a block then a > control variable could be used and exceptions avoided all together:
> enum Status { CONTINUE, BREAK; }
> class Loop implements Method1< Void, Object > { > Status status = CONTINUE; > void breakLoop() { status = BREAK; } > }
> class List { > private Object[] data; > ... > void each( Loop l ) { > for ( int i = 0; i < data.length && l.status == CONTINUE; i++ ) > { l.call( data[ i ] ); } > } > }
> // My favourite syntax follows! > list.each method( e ) { if ( someCondition ) { breakLoop() } };
but you are not showing that the call to breakLoop() has to be at a position where no further commands follow. so if you have
while (true) { println "1" if (b) break; println "2"
}
you would have to split it in a b==true and b!=true part:
while (doLoop) { if (b) { println "1" doLoop=false } else { println "1" println "2" }
}
which might have less exit points, but is also less readable in my eyes. All in all it seems I will be no fan of Tennent's idea. In my last post I used something compareable to your breakLoop() command, but I had in mind that it throws an exception, that is cached before the block is left and then a iterator method from outside could handle that.... in your case, instead of doing a simple l.call you would have to put a try-catch around to be able to skip parts of the contents of your Loop subclass.
On 10/29/07, hlovatt <howard.lov...@gmail.com> wrote:
> Tennent's book is quite old and talks about Pascal, I don't recall any > specific discussion of exceptions and exceptions were not part of > standard Pascal. So my guess is that any form of non-local jumps would > not be covered by Tennent directly. The princiople says that the > meaning of the code should not change if enclosed in a block, e.g. > while loop, but it means any type of block and therefore also a try > catch block.
That is not Tennent's Correspondence Principle.
So my reading is that any form of non-block structured jump is
> excluded by the principle, i.e. no return, break, continue, or throw > statements. All these have to be simulated with a status variable.
Your reasoning isn't valid (that Tennent applied his principles to analyze Pascal, and Pascal doesn't have multiple exit points, and therefore Tenent's principles don't support multiple exit points).
Interestingly if an inner class was used instead of a block then a
> control variable could be used and exceptions avoided all together:
If assembly language were used instead of a high-level language, one could avoid blocks, inner classes, exceptions, and variables too!