Using foreach for control instead of Closures

Overview

While there is no doubt Closures are very powerful concept they are fairly complex to implement properly and efficiently.
However, one of the use cases, control block can be simulated using the for each loop without limiting functionality of the blocks involved.

The approach

This approach uses Java 5's for each loop and Iterable/Iterators in a novel way to provide the control functionality.

A single control structure.

In the following example, the lock is acquired the first time the loop is entered and unlocked as the loop exits.

LockTest.java
for (ReentrantLock lock : withLock(getLock())) {
    assertTrue(lock.isHeldByCurrentThread());
    assertTrue(lock.isLocked());
}

assertFalse(getLock().isHeldByCurrentThread());
assertFalse(getLock().isLocked());

Multiple control structures.

The following example has three "loops". The two outer "loops" only loop once but it is a loops ability to return to a previous object which provides closure.

The first loop starts a timer which prints the time taken when the loop finishes. The second loop closes the "out" PrintWriter when it completes . The third loop reads a file one line at a time and closes the resource when finished.

CopyFile.java
public static void main(String... args) throws FileNotFoundException {
    for (Void _ : time("file copy"))
        for (PrintWriter out : closeAfter(new PrintWriter(args[1])))
            for (String line : fileByLine(args[0]))
                out.println(line);
}

The implementation

Controls.time()
public static Iterable<Void> time(final String message) {
    return new Once<Void>() {
        long start = System.nanoTime();

        public void after() {
            System.out.println(message + " took " + (System.nanoTime() - start) / 1000 / 100 / 10.0 + " ms.");
        }
    };
}
Controls.withLock()
public static <L extends Lock> Iterable<L> withLock(final L lock) {
    return new Once<L>() {
        public void before() {
            lock.lock();
        }

        public L value() {
            return lock;
        }

        public void after() {
            lock.unlock();
        }
     };
}
Controls.closeAfter()
public static <C extends Closeable> Iterable<C> closeAfter(final C closeable) {
    return new Once<C>() {
        public C value() {
            return closeable;
        }

        public void after() {
            System.out.println("<< close " + closeable + ">>");
            try {
                closeable.close();
            } catch (IOException ignored) {
                // ignored.
            }
        }
    };
}
Controls.fileByLine()
public static Iterable<String> fileByLine(final String filename) throws FileNotFoundException {
    final BufferedReader br = new BufferedReader(new FileReader(filename));

    return new ManyValues<String>() {
        public String value() throws Exception {
            return br.readLine();
        }

        public void after() {
            try {
                br.close();
            } catch (IOException ignored) {
                // ignored.
            }
        }
    };
}

Limitations.

When the loop break unexpectedly, there is no guarentee when, if ever, the iterator will be closed/finalised.
Escape analysis in Java 7 should help with this as the variable created could be added to the local stack. However ther may still be unexpected behaviour.

One way to support reliable handling is to ensure that the Iterable is closed as soon as the loop exists. The CloseableIterator interface exposes a close method which could be called.
However, it should be possible for this method to be called implicitly without the developer needing to remember to do this.

This can be done two ways:

  1. code injection to add a finally { if (iterator instanceof CloseableIterator) ((CloseableIterator) iterator.close()) } block. This could be added to Java 5.
  2. support in the compiler to do this for you. This could be added to Java 7+

Other control blocks.

  • An iterator which takes an SQL String and provides a ResultSet foreach result and closes the query when finished. Note: it could return the same resultSet object each time.

Source

The full source and unit tests are available at https://essence.svn.sourceforge.net/svnroot/essence/trunk/essence-control
The built JAR can be downloaded from https://sourceforge.net/project/showfiles.php?group_id=182039

See also

http://www.jroller.com/scolebourne/entry/closures_comparing_control_structures_of
http://www.javac.info/consensus-closures-jsr.html
http://tech.puredanger.com/java7/#closures
http://www.artima.com/weblogs/viewpost.jsp?thread=182412

Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.
  1. Apr 26, 2008

    Anonymous says:

    Re: "While there is no doubt Closures are very powerful concept they are fairly ...

    Re: "While there is no doubt Closures are very powerful concept they are fairly complex to implement properly and efficiently."

    Why should we care how complex it is to implement? The implementation is done once by Sun Microsystems, but every Java programmer reaps the benefits.  HotSpot is already amazingly complex, but having such system code isolate the complexity from user programs is a benefit, not a drawback.

    1. Apr 27, 2008

      Peter Lawrey says:

      "Why should we care how complex it is to implement?" Good question. I would say...

      "Why should we care how complex it is to implement?"
      Good question. I would say that Closures are more powerful and more complex than Generics.
      Generics in Java 5 & 6 still have many things which are not implemented properly, many edge cases most people don't understand and few programmers who can use it effectively.
      AFAIK, Sun haven't integrated Closures into Java 7 EA due to resource restrictions. While they might get into Java 7 later in the release process, I am willing to bet its going to be like Generics, only more so.

      "HotSpot is already amazingly complex,"
      Actually I would say the HotSpot JVM is not so complex. Its the Java library base and the Java language which is increasingly complex. The underlying JVM hasn't changed much in terms of the features supported. e.g. the JVM doesn't really support inner classes so the javac creates accessor methods to allow inner classes to access private members of outer classes. (Rather than the JVM understanding inner classes and allowing this)

      "having such system code isolate the complexity from user programs is a benefit, not a drawback."
      I agree, reducing and hiding complexity is an asset. However, these systems always have edge cases, it just depends on how bad they are and whether you have the learn these the hard way.

      Even simple operations like + and == have edge cases.

  2. Apr 30, 2008

    Anonymous says:

    This is quite a clever way to achieve the effect of control invocation in Java 5...

    This is quite a clever way to achieve the effect of control invocation in Java 5, although it is undoubtedly more in the 'hack' department. The ARM closure proposal - linked to CICE - approaches the problem in a not dissimilar way.

    Stephen Colebourne 

    1. May 16, 2008

      Peter Lawrey says:

      As you comment in your blog, "ARM proposal is that control structures should onl...

      As you comment in your blog, "ARM proposal is that control structures should only be added by language designers, and should not be able to be added to libraries."

      This is potentially a serious limitation, however I don't believe this has to be the case. The approach here is to provide hooks, like before(), after() which could be called and the only real addition proposed is a method which is always called when the loop ends.

Add Comment