I have moved!

I've moved my blog
CLICK HERE

Tuesday, 3 February 2009

Further Muddying the ForEach Waters

ForEach is regularly proposed as a missing feature in the BCL but has been rejected in the past apparently because it wouldn't be "functional" enough, doesn't return anything so calls cannot be chained together in a LINQ-like pipeline, and so on. So here's a different approach that is undeniably functional, and does support chaining together.

public static IEnumerable<Action> ForEach<T>(this IEnumerable<T> source, 
                                             Action<T> action)
{
    foreach (T item in source)
    {
        T captured = item;
        yield return () => action(captured);
    }
}

That extension method turns a list of items of any type into a list of actions, by binding a dedicated action to each item. The resulting list can then be operated on further - e.g. interleaved with other lists, reversed, counted, and so on.

Another useful operation is a specialised form of Aggregate that turns a list of actions into a single action. The CLR already has an efficient representation of this: a multicast delegate, so that's what I prepare and return:

public static Action Aggregate(this IEnumerable<Action> actions)
{
    Action del = null;

    foreach (Action action in actions)
        del += action;

    return del;
}

So now I can write things like:

int[] arr = { 3, 1, 4 };

Action act = arr.ForEach(Console.WriteLine)
            .Concat(arr.ForEach(Console.WriteLine).Reverse())
            .Aggregate();

act();

(Oh, how much clearer that would look with operator overloading through extension methods, using + to concatenate the lists, but I digress...)

Which prints:

3
1
4
4
1
3

So the bound actions generated from the lists can be chained, reversed, rechained, signed in triplicate and finally buried for three months and then recycled as firelighters.

By the way, the feedback from MS on the plain ForEach suggestion is baffling in any case. They don't want to support stateful, imperative programming? Then they need to remove quite a lot of existing features from the language, such as assignment! By all means support functional programming smoothly with C# (a brilliant strategy) but don't get confused into thinking that you're maintaining F#. The whole point of C# is to be the number 1 language for building and using libraries in the CLR, and such libraries are expressed through components that are, for better or worse, brimming with mutable state.

Meanwhile, you can add features for specifying whether a given data structure is mutable or not, and this will take control of naughty mutation far more effectively than merely failing to add frequently useful features to the BCL.

2 comments:

Marcel said...

yield return () => action(captured);

Why not simply

yield action(captured); ?

Your way would make it double-lazy - one would have to enumerate the IEnumerable and then explicitly invoke the action with (); why would that be needed? (If it was something you needed for a particular purpose, or if you think it makes it more generic then fine, I just can't find a reason for it myself.)

Daniel said...

You need to get rid of that "yield" in "yield action(captured)".

What you are describing is the usual definition of ForEach, which I discussed here: http://incrediblejourneysintotheknown.blogspot.com/2008/06/classic-delegateforeach-interaction-bug.html

I still think that simple definition should be added to the BCL. The point here is to show an alternative way of defining it that treats the actions as another list, and so supports chaining. Those who say chaining is important do not specify a reason why it is important - that's why I haven't gone into that. I've merely shown that it is possible to break the capability of ForEach up into stages so that chaining is possible.

(Note that the specialized version of Aggregate means that you would not have to explicitly loop through the IEnumerable and invoke each action. See the example code at the end of my post.)