9

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?

15

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.

|improve this answer|||||
  • 1
    +1 function purity is not question of pros or cons. Function purity is a hint on usage and safety. Strangely enough the OP put in where T : class, however if the OP simply put where T : strut it WOULD be pure. – ArTs May 27 '14 at 4:00
  • 4
    I disagree with this answer. Calling sequence.Apply(action) has no side effect; if it does, state the side effect that it has. Now, calling sequence.Apply(action).GetEnumerator().MoveNext() has a side effect, but we already knew that; it mutates the enumerator! Why should sequence.Apply(action) be considered to be impure because calling MoveNext is impure, but sequence.Where(predicate) be considered pure? sequence.Where(predicate).GetEnumerator().MoveNext() is every bit as impure. – Eric Lippert Jun 6 '14 at 16:29
  • @EricLippert You raise a good point. But, wouldn't it be enough to just call GetEnumerator? Can we consider that Pure? – Euphoric Jun 6 '14 at 17:29
  • @Euphoric: What observable side effect does calling GetEnumerator produce, aside from allocating an enumerator in its initial state? – Eric Lippert Jun 6 '14 at 18:50
  • 1
    @EricLippert Then why is it that Enumerable.Count is considered Pure by .NET's Code Contracts? I don't have link, but when I play with it in visual studio, I get warning when I use custom non-pure count, but the contract works just fine with Enumerable.Count. – Euphoric Jun 6 '14 at 21:04
18

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.

|improve this answer|||||
  • 2
    I guess this method falls in the same bag as the ForEach extension method, which is intentionally not part of Linq because its goal is to produce side effects... – Thomas Levesque Jun 6 '14 at 18:07
  • 1
    @ThomasLevesque: My advice is to never ever do that. A query should answer a question, not mutate a sequence; that's why they're called queries. Mutating the sequence as it is queried is extraordinarily dangerous. Consider for example what happens if such a query is then subjected to multiple calls to Any() over time; the action will be performed again and again, but only on the first item! A sequence should be a sequence of values; if you want a sequence of actions then make an IEnumerable<Action>. – Eric Lippert Jun 6 '14 at 18:48
  • 2
    This answer muddies the waters more than it illuminates. While everything you say is unquestionably true, the principles of immutability and purity are high-level programming language principles, not low-level implementation details. Programmers working at a functional level are interested in how their code behaves at the functional level, not whether or not its inner workings are pure. They're almost certainly not pure under the hood if you go low enough. We all generally run these things on Von Neumann architecture, which is most certainly not pure. – Robert Harvey Jun 12 '14 at 23:41
  • 2
    @ThomasEding: The method doesn't call action, so the purity of action is irrelevant. I know it looks like it calls action, but this method is a syntactic sugar for two methods, one which returns an enumerator, and one which is the MoveNext of the enumerator. The former is clearly pure, and the latter clearly is not. Look at it this way: would you say that IEnumerable ApplyIterator(whatever) { return new MyIterator(whatever); } is pure? Because that's the function that this really is. – Eric Lippert Aug 20 '14 at 20:10
  • 1
    @ThomasEding: You are missing something; that's not how iterators work. The ApplyIterator method returns immediately. No code in the body of ApplyIterator is run until the first call to MoveNext on the returned object's enumerator. Now that you know that, you can deduce the answer to this puzzle: blogs.msdn.com/b/ericlippert/archive/2007/09/05/… The answer is here: blogs.msdn.com/b/ericlippert/archive/2007/09/06/… – Eric Lippert Aug 20 '14 at 22:13
3

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.

|improve this answer|||||
  • Well, it doesn't modify the original collection, since it just returns a new sequence with the same items; this is why I was considering making it pure. But it might change the state of the items when you enumerate the result. – Thomas Levesque May 26 '14 at 19:41
  • If item is a reference type, it's modifying the original collection, even though you are returning item in an iterator. See stackoverflow.com/questions/1538301 – Robert Harvey May 26 '14 at 19:43
  • 1
    Even if he deep-copied the collection it still wouldn't be pure, as action may have side effects other than modifying the item passed to it. – Idan Arye May 26 '14 at 21:16
  • @IdanArye: True, the Action would also have to be pure. – Robert Harvey May 26 '14 at 22:08
  • 1
    @IdanArye: ()=>{} 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. – Eric Lippert Jun 6 '14 at 16:21
0

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.

|improve this answer|||||

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.