この方法は純粋ですか?


9

次の拡張メソッドがあります。

    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;
        }
    }

シーケンスの各アイテムにアクションを適用してから、それを返します。

このメソッドにPure(Resharperアノテーションからの)属性を適用する必要があるかどうか疑問に思っていました。そのためと反対の引数を確認できます。

長所:

  • 厳密に言えば、それ純粋です。シーケンスでそれを呼び出すだけでは、シーケンスは変更されません(新しいシーケンスが返されます)、または監視可能な状態の変更は行われません
  • 結果を使用せずに呼び出すと、シーケンスが列挙されない限り効果がないため、間違いです。その場合は、Resharperに警告を表示します。

短所:

  • にもかかわらず、Applyこの方法自体は、得られた配列は、列挙、純粋であろう(この方法の点で)観察状態を変更します。たとえば、items.Apply(i => i.Count++)列挙されるたびにアイテムの値を変更します。したがって、Pure属性を適用すると誤解を招く可能性があります...

どう思いますか?属性を適用する必要がありますか?


回答:


15

いいえ、副作用があるため、純粋ではありません。具体的にはaction、各アイテムを呼び出しています。また、スレッドセーフではありません。

純粋な関数の主な特性は、何度でも呼び出すことができ、同じ値を返す以外には何もしないということです。あなたのケースではありません。また、純粋であることは、入力パラメーター以外のものを使用しないことを意味します。つまり、任意のスレッドからいつでも呼び出すことができ、予期しない動作を引き起こすことはありません。繰り返しますが、これは関数の場合ではありません。

また、関数の純度は長所と短所の問題ではありません。副作用がある可能性があるという単一の疑いでさえ、それを純粋ではないようにするのに十分です。

Eric Lippertは良い点を挙げています。カウンター引数の一部としてhttp://msdn.microsoft.com/en-us/library/dd264808(v=vs.110).aspxを使用します。特にライン

純粋なメソッドは、純粋なメソッドへのエントリ後に作成されたオブジェクトを変更することができます。

次のようなメソッドを作成するとします。

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

最初に、これGetEnumeratorもそれが純粋であることを前提としています(私はそれについてソースを実際に見つけることができません)。そうである場合は、上記のルールに従って、このメソッドに[Pure]の注釈を付けることができます。これは、本体自体の中で作成されたインスタンスのみを変更するためです。その後、これとを構成できます。これApplyIteratorにより、純粋な関数が生成されます。

Count(ApplyIterator(source, action));

いいえ。この構成は、両方CountApplyIteratorが純粋であっても、純粋ではありません。しかし、私はこの議論を間違った前提で構築しているのかもしれません。メソッド内で作成されたインスタンスが純正ルールから除外されるという考えは間違っているか、少なくとも十分に具体的ではないと思います。


1
+1関数の純度は、賛否両論の問題ではありません。関数の純度は、使用方法と安全性のヒントです。奇妙なことに、OPはに入れましたがwhere T : class、OPが単純にwhere T : strutそれを入れた場合は純粋になります。
ArTは2014年

4
私はこの答えに同意しません。呼び出しにsequence.Apply(action)は副作用がありません。もしそうなら、それが持っている副作用を述べなさい。さて、呼び出しにsequence.Apply(action).GetEnumerator().MoveNext()は副作用がありますが、私たちはすでにそれを知っていました。列挙子を変更します!sequence.Apply(action)呼び出しMoveNextが不純でsequence.Where(predicate)純粋であると見なされるため、不純であると見なされる必要があるのはなぜですか? sequence.Where(predicate).GetEnumerator().MoveNext()不純であるとしてすべてのビットです。
Eric Lippert

@EricLippertあなたは良い点を上げます。しかし、GetEnumeratorを呼び出すだけで十分ではないでしょうか。それを純粋と見なすことができますか?
陶酔感

@Euphoric:GetEnumerator初期状態で列挙子を割り当てることを除いて、呼び出しはどのような目に見える副作用を生み出しますか?
Eric Lippert、2014

1
@EricLippertでは、なぜEnumerable.Countが.NETのコードコントラクトによって純粋と見なされるのでしょうか。リンクはありませんが、Visual Studioでプレイすると、カスタムの非純粋カウントを使用すると警告が表示されますが、Enumerable.Countでコントラクトは正常に機能します。
陶酔

18

EuphoricRobert Harveyの両方の答えには同意しません。もちろん、それは純粋な関数です。問題はそれです

シーケンスの各アイテムにアクションを適用してから、それを返します。

最初の「それ」が何を意味するかは非常に不明確です。「それ」がそれらの機能の1つを意味する場合、それは正しくありません。これらの関数はどちらもそれを行いません。MoveNextシーケンスの列挙子のは、それを行う、とビアその「リターン」の項目Currentプロパティではなく、それを返すこと。

これらのシーケンスは、熱心にではなく遅延的に列挙されるため、シーケンスがによって返される前にアクションが適用されることは確かにありません。列挙子で呼び出された場合、アクションはシーケンスが返されたに適用さます。ApplyMoveNext

お気づきのように、これらの関数はアクションとシーケンスを実行し、シーケンスを返します。出力は入力に依存し、副作用は発生しないため、これらは純粋な関数です。

ここで、結果のシーケンスの列挙子を作成してから、そのイテレーターでMoveNextを呼び出すと、アクションを呼び出して副作用が生じるため、MoveNextメソッドは純粋ではありません。しかし、MoveNextは列挙子を変更するため、純粋ではないことはすでにわかっていました。

さて、あなたの質問に関して、あなたは属性を適用するべきですか?私は最初にこのメソッドを書かないので属性を適用しません。シーケンスにアクションを適用したい場合は、

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

それは明確です。


2
このメソッドはForEach拡張メソッドと同じバッグに入ると思います。これは、副作用を生み出すことを目的としているため、意図的にLinqの一部ではありません...
Thomas Levesque

1
@ThomasLevesque:私のアドバイスは、これ絶対にしないことです。クエリは、シーケンスを変更するのではなく、質問回答する必要があります。それがクエリと呼ばれる理由です。クエリされたシーケンスを変更することは非常に危険です。たとえば、そのようなクエリがAny()時間の経過とともに複数の呼び出しを受けた場合にどうなるかを考えてみてください。アクションは何度も実行されますが、最初のアイテムのみです!シーケンスは値のシーケンスでなければなりません。一連のアクションが必要場合は、IEnumerable<Action>
Eric Lippert

2
この答えは、それが照らすよりも水を濁らせます。あなたが言うすべては間違いなく真実ですが、不変性と純粋さの原則は高レベルのプログラミング言語の原則であり、低レベルの実装の詳細ではありません。機能レベルで作業するプログラマは、コードの機能が内部で純粋であるかどうかはなく、機能レベルでどのように動作するかに関心がありますあなたが十分に低くなるならば、 彼らはフードの下でほとんど確かに純粋ではありません。私たちは皆、これらのことをフォンノイマンアーキテクチャで実行していますが、これは間違いなく純粋ではありません。
ロバートハーベイ

2
@ThomasEding:メソッドはを呼び出さないactionため、の純度actionは関係ありません。私はそれがを呼び出すように見えることを知っていますactionが、このメソッドは2つのメソッドの構文糖衣です。1つは列挙子を返し、もう1つMoveNextは列挙子のです。前者は明らかに純粋であり、後者は明らかにそうではありません。このように見てください:それIEnumerable ApplyIterator(whatever) { return new MyIterator(whatever); }は純粋だと言えますか?これが本当にある機能だからです。
Eric Lippert、

1
@ThomasEding:何かが足りない。それはイテレータが機能する方法ではありません。ApplyIteratorこの方法は返しすぐ。の本文のコードは、返されたオブジェクトの列挙子に対するApplyIterator最初の呼び出しまで実行されませんMoveNext。これがわかったので、このパズルの答えを推測できます:blogs.msdn.com/b/ericlippert/archive/2007/09/05/… 答えはここにあります:blogs.msdn.com/b/ericlippert/archive /
2007/09/06

3

これは純粋な関数ではないため、Pure属性を適用すると誤解を招きます。

純粋な関数は元のコレクションを変更せず、影響のないアクションを渡すかどうかは関係ありません。その意図は副作用を引き起こすことなので、それはまだ不純な機能です。

関数を純粋にする場合は、コレクションを新しいコレクションにコピーし、アクションが取る変更を新しいコレクションに適用し、元のコレクションを変更せずに新しいコレクションを返します。


まあ、元のコレクションは変更しません。同じアイテムの新しいシーケンスを返すだけだからです。だから私はそれを純粋にすることを考えていました。しかし、結果を列挙すると、アイテムの状態が変わる可能性があります。
Thomas Levesque、2014年

itemが参照型の場合item、イテレータで返されていても、元のコレクションを変更しています。stackoverflow.com/questions/1538301を
ロバートハーベイ、

1
コレクションにディープコピーした場合でもaction、渡されたアイテムを変更する以外の副作用がある可能性があるため、コレクションは純粋ではありません。
Idan Arye、2014年

@IdanArye:確かに、アクションも純粋でなければなりません。
Robert Harvey

1
@IdanArye:()=>{}アクションに変換可能で、純粋な関数です。出力は入力のみに依存し、目に見える副作用はありません。
Eric Lippert

0

私の意見では、アクションを受け取る(PureActionのようなものではない)という事実は、純粋ではありません。

そして、私はエリック・リッパートにさえ同意しません。彼は、「()=> {}はアクションに変換可能であり、純粋な関数です。出力は入力にのみ依存し、目に見える副作用はありません」と書いています。

デリゲートを使用する代わりに、ApplyIteratorがActionという名前のメソッドを呼び出していたとしましょう。

Actionが純粋な場合、ApplyIteratorも純粋です。Actionが純粋でない場合、ApplyIteratorを純粋にすることはできません。

デリゲートタイプ(実際の指定値ではない)を考慮すると、それが純粋であるという保証はありません。そのため、メソッドは、デリゲートが純粋である場合にのみ、純粋メソッドとして動作します。したがって、本当に純粋にするためには、純粋なデリゲートを受け取る必要があります(存在する場合、デリゲートを[Pure]として宣言できるため、PureActionを使用できます)。

異なる方法で説明すると、Pureメソッドは、同じ入力を与えられた場合、常に同じ結果を与えるはずであり、観察可能な変化を生成するべきではありません。ApplyIteratorには同じソースとデリゲートを2回指定できますが、デリゲートが参照タイプを変更している場合、次の実行では異なる結果が得られます。例:デリゲートはitem.Content + = "Changed";のようなことをします。

したがって、「文字列コンテナ」(文字列型のContentプロパティを持つオブジェクト)のリストに対してApplyIteratorを使用すると、次のような元の値が得られます。

Test

Test2

最初の実行後、リストは次のようになります。

Test Changed

Test2 Changed

そしてこれは3回目です:

Test Changed Changed

Test2 Changed Changed

したがって、リストの内容を変更しています。デリゲートは純粋ではなく、3回呼び出された場合に呼び出しが3回実行されるのを回避するための最適化を行うことができないためです。実行ごとに異なる結果が生成されるためです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.