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