jastrachan | 31 Jan 21:05 2005
Picon

thoughts on the closure/return/break/continue issue

Just a quick summary on the recent closure / return / break / continue 
discussions. I've tried to focus on simple rules which will be easy to 
understand from a Java developers view; yet do the kinds of things that 
folks using closures/blocks do in other languages which have exploited 
the concept of closures/blocks successfully in the past...

I think its a good guideline that return, break, continue (with and 
without labels) should work the same in a foo.each {} closure as they 
do in a for loop. e.g. Chris's example...

for (i in list) {
   if (something) {
     break
   }
   else if (whatnot) {
     return i
   }
}

could be written as

list.each {|i|
   if (something) {
     break
   }
   else if (whatnot) {
     return i
   }
}

i.e. we should be able to cut and paste the same code from a for() 
block to a .each {} closure block and it should work the same. We 
shouldn't have to translate the grammar just because we chose to use a 
method & closure block rather than using the for/while loop

return rules
============

So return, break, continue should work the same at any point in a 
method body - whether it happens to be in a closure block or not.

The basic rule on return is that it is mandatory at the end of any 
non-void method body. Clearly there is no equivalent 'return' inside 
the closure call as return only means, return from a method call (not a 
closure invocation).

For closures, the last expression in the closure is returned to the 
invoker of the closure - or null if there is no expression or the 
expression has no value.

no dumb expression rule
=======================

We have a no-dumb-expression rule to catch bad expression & typos (like 
line terminators in the wrong place)

def foo() {
   x = 5
   +5	// dumb expression, probably didn't mean a line terminate here
   return 2
}

no dumb expression rule in closures
===================================
We allow the last expression inside a closure to be a dumb expression, 
but previous ones cannot be.

list.each { def y = it + 1; +5; y }  // +5 is a dumb expression

terminating a closure invocation early
======================================

So how do we terminate closure invocations, since return isn't allowed? 
Normally in method bodies we can use return to stop (and if necessary 
return a value) from some nested loop structure.

Firstly here's an example of how we do it in for loops..

for (i in list) {
   if (something) {
     continue
   }
   doSomething()
}

To do a kinda return from a closure invocation, really what we're 
doing, if we consider the iteration example above - is to continue on 
to the next iteration, stopping execution of the current block.

So we should be able to do the same in .each()

list.each {|i|
   if (something) {
     continue
   }
   doSomething()
}

Now when using for(), the block of statements return no value, so a 
simple continue is enough.

However if we are invoking a closure and using the value returned, such 
as if we are adding up items in a list, then we should be able to 
associate an expression with continue.

e.g.

list.sum {|i|
   if (something) {
     continue 5
   }
   doSomething()
}

e.g. if we wanted to sum the squared value of all of the even numbers 
in a list...

list.sum {|i|
   if (i % 2 == 1) {
     continue 0
   }
   i * i
}

Normally the continue grammar is

continue [<label>]

so we have extended this above as

continue [label] | [<expression>]

So 'continue' becomes the kinda 'return from a closure invocation' 
statement - and 'return' remains the 'return from method body' 
statement.

But there is an ambiguity with the above continue syntax, if we have a 
variable and label of the same name...

label = 123
foo.each {
   if (something) {
     // is this continue-to-label or with-expr
     continue label
   }
}
y = 44
label: println "Hey"

We could say that you are not allowed to have a label with the same 
name as a local variable or field (i.e. that could be confused with an 
identifier). So we could say that the above is a compile error, as 
'label' is ambiguous. i.e. a continue <identifier> statement can only 
have one possible definition of the identifier; if there is a vanilla 
name (field, local variable, parameter) with the same name as a label 
then its a compile error.

This seems reasonable - as its quite easy to check and enforce and 
seems to match the 'do what I mean' rule.

Another option is to make this difference explicit in the grammar via

continue [<label>] | [<continueSeparator> <expression>]

Inside a closure block, we may wish to 'continue to label' or 'continue 
with expression' where <label> and <identifier> are ambiguous - so lets 
differentiate in them in the grammar. (BTW would we ever want to 
continue-to-label with an expression being passed? I don't think so - 
can anyone think of a use case?)

So how could we disambiguate between a label and expression. Here are 
some examples...
// continue to a label
     continue x

// continue with expression
     continue | x
     continue : x
     continue (x)

It might be simpler and easier to read if we could just use an 
expression and figure out if its a label or not - and enforce that you 
cannot continue to a vanilla name which is ambiguous (there is a label 
and local variable, parameter or field with the same name)?

I think this is my preferred solution as its cleaner for the common 
case; so

    continue <identifier>

the identifier is assumed to be a label and if not, its assumed to be 
an expression - but the compiler will generate a compile error/warning 
if there is a label and a suitable vanilla name available (parameter, 
local variable, field) which could conflict.

The nice thing about this is that the continue statement then works 
like the return statement...

return [<expression>]
continue [<expression>]

The main difference being, the case of

continue <identifier>

will look for a label name and use that, otherwise it'll use a 
variable/field/parameter expression (and perform the check that its not 
ambiguous).

James
-------
http://radio.weblogs.com/0112098/


Gmane