2009-03-28 17 views
4

私は関数プログラミングについて少し勉強しています。私は疑問に思っています:このC#拡張メソッドは純粋ではありませんか?

1)私のForEach拡張メソッドが純粋ならば?私がそれを呼び出す方法は、 "オブジェクトが渡されることで混乱しないでください"という違反のようです。

public static void ForEach<T>(this IEnumerable<T> source, Action<T> action) 
{ 
    foreach (var item in source) 
    action(item); 
} 


static void Main(string[] args) 
{ 
    List<Cat> cats = new List<Cat>() 
    { 
     new Cat{ Purring=true,Name="Marcus",Age=10}, 
     new Cat{ Purring=false, Name="Fuzzbucket",Age=25 }, 
     new Cat{ Purring=false, Name="Beanhead",Age=9 }, 
     new Cat{Purring=true,Name="Doofus",Age=3} 
    }; 


    cats.Where(x=>x.Purring==true).ForEach(x => 
    { 
     Console.WriteLine("{0} is a purring cat... purr!", x.Name); 
    }); 

    // ************************************************* 
    // Does this code make the extension method impure? 
    // ************************************************* 
    cats.Where(x => x.Purring == false).ForEach(x => 
    { 
     x.Purring = true; // purr,baby 
    }); 

    // all the cats now purr 
    cats.Where(x=>x.Purring==true).ForEach(x => 
    { 
     Console.WriteLine("{0} is a purring cat... purr!", x.Name); 
    }); 
} 

public class Cat { 
     public bool Purring; 
     public string Name; 
     public int Age; 
} 

2)不都合な場合は、悪いコードですか?私は個人的には、古いforeach (var item in items) { blah; }よりもきれいな見た目のコードを作っていると思うが、それは不純なかもしれないので、混乱を招くかもしれないと心配している。

3)voidの代わりにIEnumerable<T>が返された場合、それは悪いコードでしょうか?私はそれが不公平である限り、それは鎖を変更するものを連鎖させることを奨励するので、はい、それは非常に悪いコードになると思います。たとえば、この悪いコードですか?

// possibly bad extension 
public static IEnumerable<T> ForEach<T>(this IEnumerable<T> source, Action<T> action) 
{ 
    foreach (var item in source) 
     action(item); 

    return source; 

} 

答えて

11

不純物は必ずしも悪いコードを意味するものではありません。多くの人が、問題を解決するために副作用を使用するのは簡単で便利です。キーは最初に純粋な方法でそれを行う方法を知っているので、いつ不純物が適切かを知るでしょう:)。

。NETには型システムの純度の概念がないため、呼び出される方法によっては、任意のデリゲートを取り込む「純粋な」メソッドがいつも不潔になることがあります。たとえば、「場所」(別名「フィルタ」)は、引数を変更したりグローバルな状態を変更したりしないため、通常は純関数と見なされます。

しかし、Whereの引数の中にそのようなコードを置くのを止めるものはありません。例:

things.Where(x => { Console.WriteLine("um?"); 
        return true; }) 
     .Count(); 

これは、Whereの不正使用です。 Enumerableは、反復処理を行うときに必要な処理を実行できます。

コードが悪いですか?いいえ。foreachループを使用することは、「不純」と同じです。ソースオブジェクトを変更しています。私はいつものようなコードを書いています。いくつかの作業を呼び出すために、チェーンをいくつか一緒に選択したり、フィルタしたりして、ForEachを実行します。あなたは正しいです、それはより清潔で簡単です。

例:ObservableCollection。何らかの理由でAddRangeメソッドがありません。だから、私がそれにたくさんのものを追加したいのであれば、私は何をしますか?

foreach(var x in things.Where(y => y.Foo > 0)) { collection.Add(x)); } 

または

things.Where(x => x.Foo > 0).ForEach(collection.Add); 

私は2番目のものを好みます。少なくとも、それが最初の方法よりも悪いと解釈できるかどうかはわかりません。

悪いコードはいつですか?期待していない場所でコードを実行するとき。これはWhereを使った私の最初の例の場合です。そしてその時でさえ、範囲が非常に限定されており、使用法が明白である時があります。

チェーンのForEach

私はそのようなことを行うコードを書いています。混乱を避けるために、私は別の名前を付けます。主な混乱は、「これはすぐに評価されるのか、それとも怠けなのか」です。 ForEachは、すぐにループを実行することを意味します。しかし、IEnumerableを返すものは、アイテムが必要に応じて処理されることを意味します。だから、別の名前( "Process"、 "ModifySeq"、 "OnEach"など)を与えて怠け者にすることをお勧めします。

public static IEnumerable<T> OnEach(this IEnumerable<T> src, Action<T> f) { 
    foreach(var x in src) { 
    f(x); 
    yield return x; 
    } 
} 
+0

優れた応答。 –

6

不純なメソッドを呼び出すことができるので、純粋ではありません。私は典型的な定義によって、純度は推移的閉包であると思います。関数は、それが(直接的または間接的に)呼び出すすべての関数が純粋である場合、またはそれらの関数の効果がカプセル化されている場合にのみ純粋です(例えば、ローカル変数)。

+0

基本的には、それは不純なコードを呼び出す可能性があるので、関数の引数は不純ですか? – MichaelGG

+0

G(Func f)は、Gが純粋な場合は純粋であり、純粋な場合は純粋であり、不純な場合は純粋である可能性があるタイプシステムを想像することができます - 圧迫。 – Brian

+0

しかし、型システムに入れるまでは、純粋なHoFとそのパラメータが単純でないものを区別できないと便利ではありませんか? – MichaelGG

3

実際、ラムダ式に代入が含まれているため、この関数は定義上の意味では不完全です。代入が引数の1つに関係するのか、現在の関数の外で定義された別のオブジェクトであるのかは無関係です。という純粋なと呼ばれるためには、関数は何も副作用を持たない必要があります。 Wikipediaを参照してください。より正確な定義はありますが、関数はという純粋なとみなされるためには、関数が満たすべき2つの条件の詳細を記述します(副作用はありません)。私はラムダ式は純粋な関数として使われることを意図していると信じています(少なくとも、数学的な観点からは当初はそのように研究されていたと思います)。そのような機能が不潔であることは間違いなく価値がありますが、それはおそらく悪いことではありません。

4

はい、それは純粋ではありませんが、それは機能でさえも問題ではありません。

このメソッドは何も返さないので、何かを行うには唯一のオプションは、送信しているオブジェクトに影響するか、無関係な(コンソールウィンドウへの書き込みのような)影響を与えることです。

編集:
3番目の質問に答えるには、はい、それは悪いコードです、そうではないことをしているようです。このメソッドはコレクションを返すので純粋に見えますが、送信されたコレクションを返すだけで、実際は最初のバージョンよりも純粋ではありません。どんな意味を作るための方法は、変換として使用するFunc<T,T>デリゲートを取ると、変換されたアイテムのコレクションを返す必要があります:

public static IEnumerable<T> ForEach<T>(this IEnumerable<T> source, Func<T,T> converter) { 
    foreach (T item in source) { 
     yield return converter(item); 
    } 
} 

それは内線通話が純粋であればコンバータ機能までまだもちろんです。入力項目のコピーを作成せずにそれを変更して返すだけであれば、依然として純粋ではありません。

+0

数学的な意味での関数ではない、私はあなたが意味すると確信しています... – Noldorin

+0

@ Noldorin:いいえ、それは私が意味するものではありません。それは戻り値を持たないので、関数ではありません。 – Guffa