Yield considered harmful

Alessio Stalla alessiostalla at gmail.com
Fri Jul 23 00:49:02 PDT 2010


On Fri, Jul 23, 2010 at 1:28 AM, Steven Simpson <ss at comp.lancs.ac.uk> wrote:
> 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.

I really like this proposal! It allows to have both functions and
blocks but it doesn't need two different syntaxes for each of them, it
doesn't introduce new keywords nor it changes the meaning of existing
ones, it doesn't need to change the type system... it would also help
wrt. exception transparency:

public class BlockExit<throws E> { ... }

static <throws E> BlockExit<E> withLock(Lock lock, #BlockExit<E>() block);

Alessio


More information about the lambda-dev mailing list