Question

I have the following extension method:

    public static IEnumerable<T> Apply<T>(
        [NotNull] this IEnumerable<T> source,
        [NotNull] Action<T> action)
        where T : class
    {
        source.CheckArgumentNull("source");
        action.CheckArgumentNull("action");
        return source.ApplyIterator(action);
    }

    private static IEnumerable<T> ApplyIterator<T>(this IEnumerable<T> source, Action<T> action)
        where T : class
    {
        foreach (var item in source)
        {
            action(item);
            yield return item;
        }
    }

It just applies an action to each item of the sequence before returning it.

I was wondering if I should apply the Pure attribute (from Resharper annotations) to this method, and I can see arguments for and against it.

Pros:

  • strictly speaking, it is pure; just calling it on a sequence doesn't alter the sequence (it returns a new sequence) or make any observable state change
  • calling it without using the result is clearly a mistake, since it has no effect unless the sequence is enumerated, so I'd like Resharper to warn me if I do that.

Cons:

  • even though the Apply method itself is pure, enumerating the resulting sequence will make observable state changes (which is the point of the method). For instance, items.Apply(i => i.Count++) will change the values of the items every time it's enumerated. So applying the Pure attribute is probably misleading...

What do you think? Should I apply the attribute or not?

Was it helpful?

Solution

No it is not pure, because it has side effect. Concretely it is calling action on each item. Also, it is not threadsafe.

The major property of pure functions is that it can be called any number of times and it never does anything else than return same value. Which is not your case. Also, being pure means you don't use anything else than the input parameters. This means it can be called from any thread at any time and not cause any unexpected behavior. Again, that is not case of your function.

Also, you might be mistaken on one thing: function purity is not question of pros or cons. Even single doubt, that it can have side effect, is enough to make it not pure.

Eric Lippert raises a good point. I'm going to use http://msdn.microsoft.com/en-us/library/dd264808(v=vs.110).aspx as part of my counter-argument. Especially line

A pure method is allowed to modify objects that have been created after entry into the pure method.

Lets say we create method like this:

int Count<T>(IEnumerable<T> e)
{
    var enumerator = e.GetEnumerator();
    int count = 0;
    while (enumerator.MoveNext()) count ++;
    return count;
}

First, this assumes that GetEnumerator is pure too (I can't really find any source on that). If it is, then according to above rule, we can annotate this method with [Pure], because it only modifies instance that was created within the body itself. After that we can compose this and the ApplyIterator, which should result in pure function, right?

Count(ApplyIterator(source, action));

No. This composition is not pure, even when both Count and ApplyIterator are pure. But I might be building this argument on wrong premise. I think that the idea that instances created within the method are exempt from the purity rule is either wrong or at least not specific enough.

OTHER TIPS

I disagree with both Euphoric and Robert Harvey's answers. Absolutely that is a pure function; the problem is that

It just applies an action to each item of the sequence before returning it.

is very unclear what the first "it" means. If "it" means one of those functions, then that's not right; neither of those functions do that; the MoveNext of the enumerator of the sequence does that, and it "returns" the item via the Current property, not by returning it.

Those sequences are enumerated lazily, not eagerly so it is certainly not the case that the action is applied before the sequence is returned by Apply. The action is applied after the sequence is returned, if MoveNext is called on an enumerator.

As you note, these functions take an action and a sequence and return a sequence; the output depends on the input, and no side effects are produced, so these are pure functions..

Now, if you create an enumerator of the resulting sequence and then call MoveNext on that iterator then the MoveNext method is not pure, because it calls the action and produces a side effect. But we already knew that MoveNext was not pure because it mutates the enumerator!

Now, as for your question, should you apply the attribute: I would not apply the attribute because I would not write this method in the first place. If I want to apply an action to a sequence then I write

foreach(var item in sequence) action(item);

which is nicely clear.

It's not a pure function, so applying the Pure attribute is misleading.

Pure functions do not modify the original collection, and it doesn't matter whether you're passing an action that has no effect or not; it's still an impure function because its intent is to cause side effects.

If you want to make the function pure, copy the collection to a new collection, apply the changes that the Action takes to the new collection, and return the new collection, leaving the original collection unchanged.

In my opinion, the fact that it receives an Action (and not something like PureAction) makes it not pure.

And I even disagree with Eric Lippert. He wrote this "()=>{} is convertible to Action, and it's a pure function. It's outputs depend solely on its inputs and it has no observable side effects".

Well, imagine that instead of using a delegate the ApplyIterator was invoking a method named Action.

If Action is pure, then the ApplyIterator is pure too. If Action is not pure, then the ApplyIterator can't be pure.

Considering the delegate type (not the actual given value), we don't have the guarantee that it will be pure, so the method will behave as a pure method only when the delegate is pure. So, to make it really pure, it should receive a pure delegate (and that exists, we can declare a delegate as [Pure], so we can have a PureAction).

Explaining it differently, a Pure method should always give the same result given the same inputs and should not generate observable changes. ApplyIterator may be given the same source and delegate twice but, if the delegate is changing a reference-type, the next execution will give different results. Example: The delegate does something like item.Content += " Changed";

So, using the ApplyIterator over a list of "string containers" (an object with a Content property of type string), we may have these original values:

Test

Test2

After the first execution, the list will have this:

Test Changed

Test2 Changed

And this the 3rd time:

Test Changed Changed

Test2 Changed Changed

So, we are changing the contents of the list because the delegate is not pure and no optimization can be done to avoid executing the call 3 times if invoked 3 times, as each execution will generate a different result.

Licensed under: CC-BY-SA with attribution
scroll top