As I continue to sketch ideas for my dream programming language (if such a thing can even exist), I’ve begun to embrace the idea of “event oriented programming”. Almost everything can be reduced to an event. A simple event can be thought of as a single statement in a linear sequence of statements. In a system without control flow every event is of this type, and each line will be executed in order, performing its intended action.

We know from decades of language development that this is not enough for all but the simplest of programs. The introduction of a ‘jump’ command allowed basic function calls to occur. First you store your location, jump to a different place, then you jump back (which we call ‘return’). Conditional branching, the ‘if/else’ statements, gave even more control over the running of a program. Again, this is really just another flavor of jump, with a test being performed and a jump ignored or taken accordingly.

Without touching the more ‘exotic’ control flows—stately ones such as ‘yield’ and continuations—the next big development (in my view) was the loop. Behind the scenes it’s just another conditional jump, but we begin to see the development of a small lifecycle here. Consider the standard ‘for’ loop:

for (int i=0; i < 10; ++i) {
    // do something amazing
}

This small bit of code has several distinct states that it enters one or more times during its life. I view these as a sequence of events. There is:

  1. the declaration of the loop variable (int i)
  2. the initialization of the loop variable (i=0)
  3. the loop test (i < 10)
  4. the loop body
  5. the loop update (++i)

Events 3-5 comprise the loop construct which repeats until the test fails. However, modern languages give us a few more control flows through the keywords break and continue. To break out of a loop is to terminate unnaturally, while to complete the loop (that is, repeat until the test fails) is a natural termination. A common programming construct is to set a boolean flag when breaking, and then testing outside the loop to see how it exited. Readers familiar with the Python programming language likely know about the ability to tack on an else to the end of a loop, which executes code in the event of an unnatural termination.

continue stays within the loop, but skips the update event and instead restarts from the test. The assumption is that the test will succeed since an update has not occurred. Let us, though, for a moment, exchange the convenience of the ‘for’ loop for the simplicity of the ‘while’ loop.

while (true) {
    // do something
    // break, eventually
}

Everything is now in the hands of the programmer: without any logic to break out of the loop, the program will enter this block and run forever. Still, the basic events are present: the loop has a body, might have an update, should have a test to break, could maintain the idea of natural vs. unnatural termination, and likely declares some variables outside the loop.

With the goal of providing this level of flexibility in a self-contained loop construct that could be passed as an object, I created a Java class called the “SuperLoop”. Admittedly the syntax is rather unappealing and cluttered, due to the nature of the language, but I always enjoy a challenge. There are 6 events in total that a user can access through overriding the base methods, the only required one being the main loop body. Together they are:

  1. head
  2. test
  3. during
  4. natural
  5. unnatural
  6. tail

The head is the initializer, and is called only once before the main loop executes; the test happens at the top of each iteration; ‘during’ is the loop body; natural and unnatural endings occur alternately depending on how the loop finishes; and tail is always called after the loop and after the endings. Missing from all of this is the update, which is really just some action which occurs the bottom of the loop body. Also missing is a ‘do while’ option which, though easy to implement, is something I don’t see used very often. The user can simply add a line in the test method which returns true if the loop is in its first iteration.

A basic use case looks like this (and yes, it’s not so pretty):

public void smallestForm() {
    (new SuperLoop() {
        int lcv = 0;

        boolean test() {
            return (lcv < 10);
        }

        void during() {
            // stuff		
            lcv += 1;
        }
    }).$();
}

The $() method is the ‘execute’ method which runs the loop. Execution can be deferred by simply avoiding this call until later. Similarly, the entire object can be easily passed around and executed, even repeated as necessary (if the loop allows it). One of the things I like about turning the loop into an object is that it now fully contains all of the collateral bits like the loop counter. This is in the style of ‘Intentional’ programming. It’s easy for someone to look at the code and see that the loop variable is part of the loop, and not the surrounding program.

Now, because these are overridden methods, we cannot simply use the break and continue keywords to manipulate the loop’s flow. Instead, a Break() and Continue() method are provided to accomplish the same tasks. Java also supports the idea of labeled loops, and I have provided that functionality as well. A SuperLoop can be nested and break or continue to some ancestor. Here is a more complicated example utilizing nesting with labels:

public void labeled() {
    (new SuperLoop("outer") {
        int lcv;

        void head() {
            out("head");
            lcv = 0;
        }

        boolean test() {
            out("test");
            return (lcv < 10);
        }

        void during() {
            out("during (lcv == "+ lcv +")");

            (new SuperLoop() {
                int lcv = 0;
                Random gen = new Random();

                boolean test() {
                    return lcv < 3;
                }

                void during() {
                    out("inner loop, count = " + lcv);

                    if (gen.nextInt(10) > 6) {
                        Break("outer");
                    } else if (lcv == 2 && gen.nextBoolean()) {
                        if (gen.nextBoolean()) {
                            Continue("outer");
                        } else {
                            Continue();
                        }
                    }

                    lcv += 1;
                }

                void unnatural() {
                    out("inner loop unnatural ending");
                }
            }).$();

            lcv += 1;
        }

        void tail() {
            out("tail");
        }

        void unnatural() {
            out("outer loop unnatural ending");
        }
    }).$();
}

Again, rather verbose, but lots of control. The loop can be created with a provided String label, and the Break and Continue methods can take a label to perform their actions. Now some may be wondering how a method can be called and never return. The truth is that the magic here is accomplished by…gasp…Exceptions! The $() method of SuperLoop actually loops like this:

public final void $() {
    hasRun = true;
    boolean natural = true;
    Break ignoreBreak = null;
    Continue ignoreContinue = null;

    try {
        // head, fires once before the loop tests or starts
        head();

        // loop test
        while(test()) {
            try {
                // loop body
                during();

            // on break
            } catch (Break b) {
                if (b == BREAK || b.label.equals(this.label)) {
                    natural = false;
                    break;
                } else {
                    ignoreBreak = b;
                    throw b;
                }

            // on continue
            } catch (Continue c) {
                if (c == CONTINUE || c.label.equals(this.label)) {
                    continue;
                } else {
                    ignoreContinue = c;
                    throw c;
                }
            }
        }

        if (natural) {
            // loop completes naturally (test fails)
            natural();
        } else {
            // loop breaks out
            unnatural();
        }

        // tail, fires once at the very end ('finally')
        tail();

    } catch (Break b) {
        if (b == ignoreBreak) {
            throw b;
        } else {
            throw new LoopException("Invalid use of Break method outside of loop.");
        }
    } catch (Continue c) {
        if (c == ignoreContinue) {
            throw c;
        } else {
            throw new LoopException("Invalid use of Continue method outside of loop.");
        }
    }
}

Each instance of a SuperLoop gets its own Break and Continue objects, which are Exceptions. By throwing from child to parent, a loop can traverse its scope to find the correct label. If an exception escapes all loops it becomes a standard exception in the program, taking the form of an “invalid label” error.

Of course, all of this is laughably slow compared to a simple, language-level loop construct. Using Exceptions as control flow mechanisms is silly but it works. As well, overriding all of those methods and the ugly way of creating the object is a bit of a turn-off (I did make a SuperLoopFacade interface which exposes only the $() method for added clarity). Further, there is the lack of access to outside non-final variables. Still, the ability to define the running of a loop with increased granularity, and through a uniform creation process, is interesting. Exotic control flows, however complicated, are generally just the coordinated firing of events, and even then just a series of jumps from one place in the code to another. Every object embodies at least the basic lifecycle of create->use->destroy, but by adding in additional hooks which developers can access, simple objects can be transformed into tools in their own right.

The code for the SuperLoop class will be part of my presently vaporware Java library, which I desperately hope to release any day now. Perhaps in the future I will port the code to JavaScript or Ruby to take advantage of a simpler syntax. In the meantime you can get the Java code, along with some test cases, here.