带闭包的无错编码(在非纯虚拟环境中)

时间:2010-01-14 12:46:00

标签: c# f# closures

最后几天,我很难理解闭包。我非常喜欢C#,所以我的主要测试版是这种语言,所以我想了解它的闭包支持。在我研究和实验的过程中,我发现许多人在尝试写关于闭包的博客时,他们是通过遵循完全错误的方向来做到的。他们预计会使用像众所周知的for-statement这样的闭包,他们正试图解释它。相反,我希望看到一种数学方法(一等公民,自由/约束变量,lambdas等)。然而,这让我觉得我想知道在编写时没有关闭时会出现什么错误。

此外,所有语言对闭包的数学结构都有相同的解释吗?

在uni中我没有FP课程或高级编程语言。但我知道副作用在程序代码中的作用以及它们在纯虚拟语言中不存在的作用。 C#中的闭包只是一个技巧吗?什么(例如)F#闭包有多于C#闭包?

4 个答案:

答案 0 :(得分:5)

首先,我认为应该称为闭包以及应该称为 lambda函数的内容存在一些混淆。我认为正确的方法是用语言调用语法元素(例如C#中的(a, b) => a + b} lambda函数。创建的值是函数值或C#中的委托

在.NET中实现(包括F#和C#),委托实际上是对某些类中某些方法的引用。原因是使用 lambda函数语法构造的委托可能需要保留一些状态:

Func<int, int> CreateAdder(int num) {
  return arg => arg + num;
}

返回的委托引用一些(未命名的)对象,该对象存储num值以及 lambda函数的正文。那么,什么是闭包? Closure是对象,它保持运行函数值(或 delgate )所需的状态。在这种情况下,它是未命名的对象,从委托引用并保留num的值。

您还提到了免费绑定变量。如果你看一下上面例子中的 lambda函数,它可以使用两个变量。变量arg被声明为 lambda函数的一部分。这将被称为绑定变量(在 lambda函数中),因为它被声明为lambda函数的一部分。 {em} lambda函数中的num变量将被称为自由变量,因为它仅在 lambda函数。它来自外部范围(在本例中为方法声明)。

闭包需要捕获 lambda函数中的所有 free 变量。这意味着捕获在体内使用但在其他地方声明的所有变量。但是,C#编译器不只是复制当前值。它将它变成一个可变字段,这样它就可以从可能访问它的所有函数中访问(也可以变异)(这也是它变得棘手的地方)。这将是一篇长篇博文的主题,但这里有一个简短的例子,你可以用来试验这个(使用.NET 4.0中的Tuple,如果你使用的是VS 2008,你可以得到C#实现here in Chapter03/FunctionalCSharp):

Tuple<Func<int>, Action<int>> CreateReaderAndWriter(int initial) {
   int state = initial;
   return Tuple.Create( (() => state),
                        (newState => { state = newState; }) );
}

当您调用此方法时,您将获得两个函数。第一个允许您读取当前状态,第二个允许您修改它。请注意,状态是共享的(因为它是相同的可变变量)!

有一个proposal by Don Syme from MSR直接向.NET添加对闭包的支持。这有点学术性,但它可能有助于澄清一些事情。

答案 1 :(得分:4)

闭包的概念在不同语言中是相当一致的,尽管在命令式语言中对于如何处理闭包中的continuebreakreturn等构造存在一些分歧(例如,在这方面,为Java添加闭包的一些提议在行为方面与C#不同)。捕获人的主要精妙之处在于,在非纯语言中,闭包“接近”变量绑定而不是值,这意味着变量作用域之类的东西非常重要(这就是for循环示例中出现意外困难的地方,因为循环变量的范围并不总是人们所期望的。)

F#的行为与C#的行为非常相似,但是有一些东西可以使F#中的闭包效果更好。首先,虽然F#是不纯的,但不鼓励变异,因此编写一个无意中关闭变量的闭包更难,后者后来以一种打破期望的方式进行修改。特别是,F#编译器不允许在闭包内部使用正常的可变绑定 - 编译器的错误消息表明您要么使绑定不可变,要么使用显式引用单元格(在F#中键入'a ref)你实际上打算关闭一个可以变异的绑定。这迫使用户仔细思考试图实现的目标。

答案 2 :(得分:1)

听起来我想要学习一般的函数式编程。这样做,你无法避免学习使用闭包的“正确”方式,因为它们对函数式编程至关重要。

不幸的是,我不知道C#的一个好的函数式编程参考。搜索一下这篇介绍文章:Introduction to Functional Programming in C#

如果您不介意使用其他语言,可以考虑The Little Schemer。它使用Scheme,但仅使用您实际需要的部分。它很容易理解,但直接深入研究函数式编程的难点。

至于你的另一个问题,我发现如果你不改变变量,闭包在大多数语言中表现相同 - 甚至是Java的匿名内部类。 (尽管像kvb所说的那样,F#和Haskell这样的函数式语言确实可以防止你在不想要的时候错误地改变变量。)

答案 3 :(得分:0)

您是否阅读过Martin Fowler关于此主题的讨论?它似乎涵盖了您的担忧,来自一个相当权威的人物:http://martinfowler.com/bliki/Closure.html