Linq Walk

So, we all know Linq is rad. Not in that silly faux-SQL syntax, but in the manly, declarative, fluent syntax.

But the sad part is that there is no walk extension method. Walk is a functional programming staple that iterates over a collection of stuff and applies a callback to each one. In PHP, there is array_walk, in JavaScript there is each.

You might be thinking, "but there is a ForEach() extension method on List!". And you would be wrong, because there is a subtle, yet important difference between IEnumerable and IList.

IEnumerable is late-binding. That means you can do whatever you want to it, but execution is deferred until you actually enumerate the enumeration, i.e. using a foreach construct. In a list, there is no deferred execution. This deferred execution is accomplished through the magic of closures and expression trees. Read up on them.

In the meantime, here is a fluent implementation of walk:

public static class LinqExtensions {
	public static IEnumerable<T> Walk<T>(this IEnumerable<T> source, Action<T> action) {
		foreach (var t in source) {
			action(t);
			yield return t;
		}
	}
}

You can prove that this is in fact using deferred execution with a simple test:

class Program {

	class Foo {
		public int Bar { get; set; }

		public override string ToString() {
			return string.Format("Foo(Bar={0})", Bar);
		}
	}

	static void Main() {
		IEnumerable<Foo> foos = new[] { new Foo { Bar = 2 }, new Foo { Bar = 1 }, new Foo { Bar = 3 } };

		Console.WriteLine("IEnumerable.Walk():");
		foos.Walk(Console.WriteLine);
		Console.WriteLine();

		Console.WriteLine("List.ForEach():");
		foos.ToList().ForEach(Console.WriteLine);
		Console.WriteLine();

		Console.ReadLine();
	}

}

The output looks like this:

linqwalk

Notice that nothing got printed the first time through, using the Walk() extension method. There's your deferred execution. That means our extension method did not enumerate the enumeration, so it's safe and efficient to use walk in a normal linq expression where you are depending on deferred execution, like this:

IEnumerable<Foo> foos = new[] { new Foo { Bar = 2 }, new Foo { Bar = 1 }, new Foo { Bar = 3 } };

Console.WriteLine("Doing more stuff:");
var listOStuff = foos
	.Where(foo => foo.Bar >= 2)
	.Walk(foo => foo.Bar += 10)
	.Select(foo => new Baz { Foofy = foo })
	.Walk(baz => baz.Foofy.Bar--)
	.OrderBy(baz => baz.Foofy.Bar);

foreach (var stuff in listOStuff) {
	Console.WriteLine(stuff);
}

Console.ReadLine();

linqwalk2

Well, maybe that's not "normal." Whatever.