Beyond Loops

Lately, I've been reading quite a bit about the functional programming concepts that are finally coming to C#. The benefits of this new hybrid (imperative/functional) style are numerous, from greater readability (for those who grasp the new idioms, anyway) to more convenient parallelization. However, I've seen very little about the advantage that excites me most: eliminating loops.

What?!

Okay, I'm not really advocating that we actually stop writing code that contains loops—after all, the most reasonable alternative to loops is recursion, and recursion hurts my brain. I am instead suggesting that the time has come for most of our loops to be abstracted away, and here is my first attempt at hiding them:

public static class EnumerableUtility

{

  public static IEnumerable<T> Iterate<T>(T initial, Func<T, bool> fnContinue, Func<T, T> fnNext)

  {

    for (T current = initial; fnContinue(current); current = fnNext(current))

      yield return current;

  }

 

  public static void ApplyToAll<T>(this IEnumerable<T> list, Action<T> fnAction)

  {

    foreach (T item in list)

      fnAction(item);

  }

}

(Note: Did It With .NET defines a similar function, Sequence(), that operates similarly but specifies the arguments in a different order. I prefer my function, but mine could be replaced by his by calling Sequence(initial, fnContinue, fnNext). Either way, the effect is the same.)

Why is this so great? All I really did was put a couple loops into a function, right? Well…sort of, but if you'll stick with me, I think that you'll see that these functions have some significant advantages.

The Problem

In order to compute very much that is of interest to anyone, a language must support sequence, choice, and repetition. When taken in isolation, each of these concepts is easy to grasp, but when combined, they can very quickly exceed the limits of human comprehension. Personally, I find that a few big loops will tax my abilities faster than either of the other two structures, but I also find that repetition tends to be the least frequently abstracted of the three basic constructs.

New Paradigm

Let's look at some code:

var somethingOrOther = initialValue;

while (SomeCondition(somethingOrOther))

{

  if (SomeOtherCondition(somethingOrOther))

    DoStuff(somethingOrOther);

  somethingOrOther = GetNext(somethingOrOther);

}

Or its common counterpart:

for (var somethingOrOther = initialValue; SomeCondition(somethingOrOther); somethingOrOther = GetNext(somethingOrOther)))

{

  if (SomeOtherCondition(somethingOrOther))

    DoStuff(somethingOrOther);

}

The details and complexity vary, but I've seen code like this many times in my (admittedly short) career. We've been trained to accept such things as normal, but is this really as good as it gets? Here are some of the issues I see:

  • If the code is sufficiently complex, it becomes difficult to determine with certainty where somethingOrOther is assigned—especially if the original developer was undisciplined.
  • In the case of the first example, we have an iterator left outside the scope of the loop, which is just asking for trouble.
  • We're mixing repetition and choice, which will lead to confusion as the complexity of the loop grows.

However, the operations defined in EnumerableUtility allow us to think of this as a series of operations on a list, rather than as a loop:

var items = EnumerableUtility.Iterate(initialValue, SomeCondition, GetNext);

items = items.Where(SomeOtherCondition);

items.ApplyToAll(DoStuff);

How is this better? First of all, the second and third operations now appear as a single operation or a list, rather than as a series of operations or individual items—and I find this to be easier on my mental model. Second, we no longer have to concern ourselves about strange assignments to our iterator because the iterator itself has been abstracted away! Finally, having all of the primary operations (enumerating the values, filtering them, and performing an operation on the remaining values) separated from each other allows us to consider each in isolation, which is significantly easier on the brain.

Next time, I intend to introduce some common algorithms and show how using lists instead of loops can simplify their implementation.

Print | posted @ Monday, October 29, 2007 3:57 PM

Comments on this entry:

Gravatar # re: Beyond Loops
by Jim Houx at 2/20/2008 9:01 PM

Abstracting iterators to eliminate visible looping seems to be very smart. 3ds Max's MaxScript actually utilizes what they call "mapped functions" which are functions that operate on a list of items. Calling the function just once causes it to executes its code for each item in the given list. MaxScript also has collector functions which let you iterate through a set of items and build a list of the items that meet a certain condition. The functions make for extremely simple code. Interestingly, I've noticed when working with MaxScript that I find using these functions to be unfamiliar because I have accepted that hand-written loops are the standard methodology. A small amount of self-discipline to break out of old thinking and to adopt newer methods of dealing with repetition and choice could probably go a long way for myself, as well as other programmers of our current generation.

Your comment:

Title:
Name:
Email:
Website:
 
Italic Underline Blockquote Hyperlink
 
 
Please add 3 and 8 and type the answer here: