Yield considered harmful
Steven Simpson
ss at comp.lancs.ac.uk
Thu Jul 22 16:28:51 PDT 2010
Hi,
On 13/07/10 12:41, Stephen Colebourne wrote:
> The option FCM-JCA outlined followed the strategy of using syntax to
> enable two uses of "return"
>
> Syntax A example:
> forEach(list, #(str) { // looks more like an inner class
> if (str.length()> 3) return; // local-return
> System.out.println(str);
> });
>
> Syntax B example:
> list.each() for (String str) { // looks like a code block
> if (str.length()> 3) return; // lexical return
> System.out.println(str);
> }
While I like that, something else occurred to me. forEach(), along with
everything else that provides control abstraction, must accept a block,
so you need a type (or family thereof) that captures the abstraction of
a block.
What does a block do? Stuff, resulting in a return, a throw, a break, a
continue or a fallthrough. So define forEach in terms of a function
type returning a BlockExit type to represent those choices:
static<T> BlockExit forEach(Iterable<T> coll, #BlockExit(T) block) {
for (Iterator<T> iter = coll.iterator(); iter.hasNext(); ) {
BlockExit exit = block.invoke(iter.next());
if (exit.breaksLocal())
break;
if (exit.continuesLocal())
continue;
if (exit.returns())
// Go here for return, break/continue<label>, and throw.
return exit;
}
return BlockExit.FALL_THROUGH;
}
(This might be considered a loss of transparency, but I think it's
appropriate here. The author of forEach wants to be able to decide
exactly what a local break/continue means for the loop.)
BlockExit expresses the ways a block exits (surprise!):
class BlockExit {
static final BlockExit FALL_THROUGH;
boolean fallsThrough();
static final BlockExit BREAK; // unlabelled break
static BlockExit breakingTo(Label label);
boolean breaksTo(Label label);
boolean breaksLocal();
static final BlockExit CONTINUE; // unlabelled continue
static BlockExit continuingTo(Label label);
boolean continuesTo(Label label);
boolean continuesLocal();
static BlockExit throwing(Throwable t);
void doThrow() throws Throwable;
static BlockExit returningInt(int value); // for all primitives
static BlockExit returningObject(Object value);
boolean returns();
int intValue(); // for all primitives
<T> T objectValue(Class<T> type);
}
Label becomes a store for static BlockExits to labelled loops:
class Label {
BlockExit getBreakExit();
BlockExit getContinueExit();
}
Other BlockExit instances are either static finals, or could be cached
or thread-local.
When the compiler gets a call that requires a block to be interpreted as
a function returning BlockExit, it translates the control statements
into returns of type BlockExit. So this original code:
int findStuff(BufferedReader in)
throws IOException, InterruptedException {
outer:
for (String line; (line = in.readLine()) != null; ) {
List<String> list = parseLine(line);
int count = 0;
inner:
forEach(list) for (String s) {
doStuff(s);
if (umph(s))
break;
if (oof(s))
continue;
if (umph2(s))
break outer;
if (oof2(s))
continue outer;
if (argh(s))
return count;
if (flip(s))
throw new EOFException();
count++;
}
}
return -1;
}
...translates to:
private static final Label _label_outer = ...;
int findStuff(BufferedReader in)
throws IOException, InterruptedException {
outer:
for (String line; (line = in.readLine()) != null; ) {
List<String> list = parseLine(line);
int count = 0;
inner:
java.lang.function.OO<BlockExit, String> _block =
new java.lang.function.OO<BlockExit, String>() {
public BlockExit invoke(String s) {
doStuff(s);
if (umph(s))
return BlockExit.BREAK;
if (oof(s))
return BlockExit.CONTINUE;
if (umph2(s))
return _label_outer.getBreakExit();
if (oof2(s))
return _label_outer.getContinueExit();
if (argh(s))
return BlockExit.returningInt(count);
if (flip(s))
return BlockExit.throwing(new EOFException());
count++;
return BlockExit.FALL_THROUGH;
}
};
BlockExit _exit = forEach(list, _block);
try {
_exit.doThrow();
} catch (IOException|InterruptedException _ex) {
throw _ex;
} catch (RuntimeException|Error _ex) {
throw _ex;
} catch (Throwable _ex) {
throw new AssertionError();
}
if (_exit.breaksTo(_label_outer))
break outer;
if (_exit.continuesTo(_label_outer))
continue outer;
if (_exit.returns())
return _exit.intValue();
assert _exit.fallsThrough();
}
return -1;
}
When assigning a block to a function type, it wouldn't make sense to
treat returns as lexical unless a BlockExit was the function's return
type, so that would be the determinant (rather than, or in addition to,
a choice of syntax) for the meaning of 'return'. Taking it a bit
further, we could define LoopExit to extend BlockExit, and use it to
signal that the control abstraction is to be treated as a loop, thus:
static<T> BlockExit forEach(Iterable<T> coll, #LoopExit(T) block);
...where unlabelled break/continue apply to the forEach loop, whereas:
static BlockExit withLock(Lock lock, #BlockExit() block);
...where break/continue would apply to a containing loop, or would be
illegal.
Cheers,
Steven
More information about the lambda-dev
mailing list