我正在学习一些关于函数编程的知识,我想知道:
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) 如果返回 IEnumerable<T>
是否是错误代码而不是 void
?我会说只要它是不纯的,是的,它会是非常糟糕的代码,因为它会鼓励链接一些会修改链的东西。例如,这是错误的代码吗?
// 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;
}
最佳答案
不纯并不一定意味着糟糕的代码。许多人发现使用副作用来解决问题既简单又有用。关键是首先要知道如何以纯粹的方式做到这一点,这样你就会知道什么时候不纯是合适的:)。
.NET 在类型系统中没有纯粹的概念,因此接受任意委托(delegate)的“纯粹”方法总是不纯粹的,这取决于它的调用方式。例如,“Where”,又名“filter”,通常被认为是一个纯函数,因为它不修改其参数或修改全局状态。
但是,没有什么可以阻止您将此类代码放入 Where 的参数中。例如:
things.Where(x => { Console.WriteLine("um?");
return true; })
.Count();
所以这绝对是对 Where 的不纯用法。 Enumerables 可以在迭代时做任何他们想做的事。
您的代码不好吗? 不。使用 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);
我更喜欢第二个。至少,我不认为它比第一种方式更糟糕。
什么时候是坏代码?当它在意想不到的地方产生副作用代码时。这是我使用 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;
}
}
关于c# - 这个 C# 扩展方法是否不纯,如果是,代码是否错误?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/693628/