I attended Joshua Blochs’ presentation on closures at JavaPolis last week (watch the video here). This slides about return not return from what you’d expect kept me wondering: how do other languages solve this ‘problem’.
The example from Bloch, taken from Slide 38 of Blochs’ closures controversy presentation:
[java]
static
for (E e : seq)
if (p.invoke(e))
return true;
return false;
}
static Boolean test() {
List
‘h’, ‘e’, ‘l’, ‘l’, ‘o’);
return contains(list, { Character c => return c > ‘j’; } );
}
[/java]
The point Josh tried to make was that due to this ‘copy and paste’ error the contains method would return on the first invocation of the passed block. Auch….
It asked around at Morning coffee but nobody really knew what this would actually do in Ruby.
So, let’s just try it. In Ruby this might look a bit like this:
[ruby]
def contains(enumerable, &test)
enumerable.each do |e|
return true if test.call(e)
end
false
end
puts contains(['h','e','l','l','o']){|v| return v == “z”}
[/ruby]
In the Ruby a return statement in a block returns from the enclosing function as well. However, if the return statement is part of a closure passed to a function returning is not allowed:
closure_return.rb:9: unexpected return (LocalJumpError)
from closure_return.rb:4:in `call’
from closure_return.rb:4:in `contains’
from closure_return.rb:3:in `each’
from closure_return.rb:3:in `contains’
from closure_return.rb:9
Throwing an error might be considered a better solution. The problem in this case is the fact that the error is thrown at the first invocation of the block; code before the invocation of the block will be called… but code after the invocation won’t. Of coarse this is ‘just’ a matter of taking this into account:
[ruby]
def contains(enumerable, &test)
begin
puts “a”
enumerable.each do |e|
return true if test.call(e)
end
rescue
puts “Error in passed test-block: #{$!}”
ensure
puts “b”
end
false
end
puts contains(['h','e','l','l','o']){|v| return v == “z”}
[/ruby]
In this case the ensure statement will always be called. I’m not really in favor of adding closures to Java; just go to Ruby/Groovy/Scala if you want them). I do however think that the ‘return’ argument Josh made isn’t to strong; a solution similar to the way it works in Ruby could probably be found for Java.
Josh’s code is and will continue to be a compile-time error in BGGA closures.
I believe that Neal is mistaken. Here is the entire program:
import java.util.*; public class Junk4 { static boolean contains(Iterable seq, {E => Boolean} pred) { for (E e : seq) return true; return false; } static boolean test() { List list = Arrays.asList( 'h', 'e', 'l', 'l', 'o'); return contains(list, { Character c => return c > 'j'; }); } public static void main(String[] args) { System.out.println(test()); } }When compiled under the most recent BGGA prototype (closures-2007-11-30), it generates a single compiler “error”:
c:/jjb/bgga/closures-2007-11-30/bin/javac Junk4.java
Junk4.java:14: Nonlocal return not yet implemented.
return contains(list, { Character c => return c > ‘j’; });
^
1 error
In other words, the prototype BGGA compiler thinks the program is fine. (Of course the compiler doesn’t know or care how confusing it is.)
Josh
P.S. Sorry about the lost formatting. I guess I should have put in some tags.
I’ve added some tags to the post to fix the formatting.
The error message generated by the BGGA compiler does indeed suggest that non-local returns will be allowed in the future; but it something like this will probably be open for debate.
Of course the error message will be improved once the implementation supports return in the control invocation syntax. A closure written using the => syntax, or converted to a function type written using the => syntax, will be “restricted” and not allowed to return from the enclosing method. See , specifically the last two paragraphs, for a discussion of the way the semantics will work once implemented. I called this to Josh’s attention about two weeks before his presentation, so I don’t know why he included it as if it is an unsolved problem.
That link should be http://gafter.blogspot.com/2007/12/restricted-closures.html
Sorry, I see no mention of this rule (concerning the => syntax and restricted closures) in the proposal (http://www.javac.info/closures-v05.html), and I have no recollection of the conversation that Neal refers to. When I prepared the “Closures Controversy” talk, I based it on the best and most current information at my disposal. And I continue to believe that adding non-local returns to Java would add enormous and unnecessary complexity. (Of course non-local returns make good sense in languages like Ruby, which were designed around closure-based control structures.)
Josh
The closures-v05 document is quite scarce on details concerning the RestrictedInterface. The blog post on Neals’ blog is a better read to understand it.
I do however feel that this sort of behavior should be intuitive and having different types of closures doesn’t really help IMHO. Also, the solution using intersection makes method definitions far to complex! Say we also want to specify the fact that the block passed to the function might thrown an exception; we would get something like this:
public <U,V,throws E extends Exception> ParallelArray<V> combine(ParallelArray<U> other, {T,U=>V throws E}&RestrictedFunction combiner) throws E{ ... }Which is just ugly. And contrived example or not… people WILL start doing this!
Actually, I do not expect to support intersection types. Quite simply, when you write a function type using => or a closure using => it is restricted, and you can’t write a “return” inside the closure.
@nmgafter I read the paragraph about the implicit behavior of function written using => and it makes sense. I’m not to sure if having a different token for non restricted would however be a good idea…? Why would you actually want to allow non-local returns, I personally can’t think of a good case?
@peter One example of a use case would be writing an API acts as a typeswitch. Being able to “return” from the enclosing method is just as useful in a typeswitch as it is in any other kind of switch.
@peter Also see http://gafter.blogspot.com/2007/03/closures-for-organizing-your-code.html.