Mandy's Tech Blog

Sitting in relative obscurity since 2007…

View My GitHub Profile

Follow me on Twitter

Come work with me!

Site feed

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:

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.

comments powered by Disqus