什么时候关闭?

时间:2008-11-02 07:27:37

标签: c# functional-programming closures

我看过 - What is a 'Closure'?

的封闭样本

任何人都可以提供何时使用闭包的简单示例?

具体来说,闭包有意义吗?

让我们假设语言没有闭包支持,怎么会有类似的东西?

不要冒犯任何人,请用c#,python,javascript,ruby等语言发布代码示例。
对不起,我还不懂函数式语言。

9 个答案:

答案 0 :(得分:32)

闭包只是很棒的工具。什么时候使用它们?任何时候你喜欢...正如已经说过的,另一种选择是写一个班级;例如,在C#2.0之前,创建参数化线程是一个真正的斗争。使用C#2.0,您甚至不需要`ParameterizedThreadStart':

string name = // blah
int value = // blah
new Thread((ThreadStart)delegate { DoWork(name, value);}); // or inline if short

将其与创建具有名称和值的类

进行比较

或者同样搜索列表(这次使用lambda):

Person person = list.Find(x=>x.Age > minAge && x.Region == region);

再次 - 替代方法是编写一个具有两个属性和方法的类:

internal sealed class PersonFinder
{
    public PersonFinder(int minAge, string region)
    {
        this.minAge = minAge;
        this.region = region;
    }
    private readonly int minAge;
    private readonly string region;
    public bool IsMatch(Person person)
    {
        return person.Age > minAge && person.Region == region;
    }
}
...
Person person = list.Find(new PersonFinder(minAge,region).IsMatch);

相当与编译器在引擎盖下的工作方式相当(实际上,它使用公共读/写字段,而非私有只读)。

C#捕获的最大警告是观察范围;例如:

        for(int i = 0 ; i < 10 ; i++) {
            ThreadPool.QueueUserWorkItem(delegate
            {
                Console.WriteLine(i);
            });
        }

这可能无法打印您所期望的内容,因为变量 i用于每个。你可以看到重复的任何组合 - 甚至10 10。您需要在C#中仔细确定捕获的变量的范围:

        for(int i = 0 ; i < 10 ; i++) {
            int j = i;
            ThreadPool.QueueUserWorkItem(delegate
            {
                Console.WriteLine(j);
            });
        }

这里每个j都被单独捕获(即不同的编译器生成的类实例)。

Jon Skeet有一篇很好的博客文章,涵盖了C#和java闭包here;或者有关详细信息,请参阅他的书C# in Depth,其中有完整章节。

答案 1 :(得分:22)

我同意以前的“所有时间”的回答。当您使用函数式语言或lambdas和闭包很常见的语言进行编程时,您甚至不会注意到它们。这就像问“功能的场景是什么?”或“循环的场景是什么?”这不是为了让原始问题听起来愚蠢,而是要指出在语言中有一些结构,你没有根据特定的场景定义。你只是一直使用它们,对于一切,它是第二天性。

这在某种程度上让人想起:

  

尊敬的大师Qc Na正在走路   与他的学生安东。希望能   提示大师进行讨论,   安东说:“师父,我听说过   对象是一件非常好的事情 - 是   这是真的吗?“Qc Na怜悯地看着   他的学生并回答说:“愚蠢   瞳孔 - 物体只是一个穷人   男人的关闭。“

     被指责,安东离开了   他的主人回到他的牢房,   意图研究闭包。他   仔细阅读整个“Lambda:The   终极......“一系列论文及其论文   堂兄弟,并实施了一个小   计划翻译与   基于闭包的对象系统。他   学到了很多,并期待着   告诉他的主人他的进步。

     

在他与Qc Na,Anton的下次散步时   试图给他的主人留下深刻的印象   说“师父,我很努力   研究过这件事,现在明白了   那些物品真的是一个穷人   关闭。“Qc Na击中了回应   安东用他的棍子说:“什么时候   你会学习吗?关闭是一个穷人   男人的对象。“那一刻,安东   开悟了。

http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg03277.html

答案 2 :(得分:14)

使用闭包的最简单的例子是称为currying。基本上,我们假设我们有一个函数f(),当使用两个参数ab调用它们时,将它们加在一起。所以,在Python中,我们有:

def f(a, b):
    return a + b

但是,让我们说,为了争论,我们只想一次用一个参数调用f()。因此,我们需要f(2, 3)而不是f(2)(3)。这可以这样做:

def f(a):
    def g(b): # Function-within-a-function
        return a + b # The value of a is present in the scope of g()
    return g # f() returns a one-argument function g()

现在,当我们致电f(2)时,我们会收到一个新功能g();这个新函数带有来自f()范围的变量,因此它被称为关闭这些变量,因此称为闭包。当我们致电g(3)时,a访问变量f(受g()定义约束),返回2 + 3 => 5

这在几种情况下很有用。例如,如果我有一个接受大量参数的函数,但只有少数对我有用,我可以像这样写一个泛型函数:

def many_arguments(a, b, c, d, e, f, g, h, i):
    return # SOMETHING

def curry(function, **curry_args):
    # call is a closure which closes over the environment of curry.
    def call(*call_args):
        # Call the function with both the curry args and the call args, returning
        # the result.
        return function(*call_args, **curry_args)
    # Return the closure.
    return call

useful_function = curry(many_arguments, a=1, b=2, c=3, d=4, e=5, f=6)

useful_function现在是一个只需要3个参数的函数,而不是9个。我避免重复自己,并且还创建了一个泛型解决方案;如果我写了另一个多参数函数,我可以再次使用curry工具。

答案 3 :(得分:11)

通常情况下,如果一个人没有闭包,那么必须定义一个类来携带它等同于闭包的环境,然后传递它。

例如,在像Lisp这样的语言中,可以定义一个函数来返回一个函数(带有一个封闭的环境),从而为其参数添加一些预定义的数量:

(defun make-adder (how-much)
  (lambda (x)
    (+ x how-much)))

并像这样使用它:

cl-user(2): (make-adder 5)
#<Interpreted Closure (:internal make-adder) @ #x10009ef272>
cl-user(3): (funcall * 3)     ; calls the function you just made with the argument '3'.
8

在没有闭包的语言中,你会做这样的事情:

public class Adder {
  private int howMuch;

  public Adder(int h) {
    howMuch = h;
  }

  public int doAdd(int x) {
    return x + howMuch;
  }
}

然后像这样使用它:

Adder addFive = new Adder(5);
int addedFive = addFive.doAdd(3);
// addedFive is now 8.

封闭隐含地带有它的环境;您可以从执行部分(lambda)内部无缝地引用该环境。如果没有闭包,你必须明确这个环境。

这应该向您解释何时使用闭包:始终。大多数实例,其中一个类被实例化以从计算的另一部分携带一些状态并将其应用到其他地方,这些实例被支持它们的语言中的闭包优雅地取代。

可以使用闭包实现对象系统。

答案 4 :(得分:3)

以下是Python标准库inspect.py的示例。目前正在阅读

def strseq(object, convert, join=joinseq):
    """Recursively walk a sequence, stringifying each element."""
    if type(object) in (list, tuple):
        return join(map(lambda o, c=convert, j=join: strseq(o, c, j), object))
    else:
        return convert(object)

作为参数,它具有转换函数和连接函数,并以递归方式遍历列表和元组。使用map()实现递归,其中第一个参数是函数。代码早于Python中对闭包的支持,因此需要两个额外的默认参数,以将转换和连接传递给递归调用。有了闭包,这就是

def strseq(object, convert, join=joinseq):
    """Recursively walk a sequence, stringifying each element."""
    if type(object) in (list, tuple):
        return join(map(lambda o: strseq(o, convert, join), object))
    else:
        return convert(object)

在OO语言中,您通常不会经常使用闭包,因为您可以使用对象来传递状态和绑定方法,当您的语言具有它们时。当Python没有闭包时,人们说Python用对象模拟闭包,而Lisp用闭包模拟对象。以IDLE(ClassBrowser.py)为例:

class ClassBrowser: # shortened
    def close(self, event=None):
        self.top.destroy()
        self.node.destroy()
    def init(self, flist):
        top.bind("<Escape>", self.close)

这里,self.close是按下Escape时调用的无参数回调。但是,close实现确实需要参数 - 即self,然后是self.top,self.node。如果Python没有绑定方法,您可以编写

class ClassBrowser:
    def close(self, event=None):
        self.top.destroy()
        self.node.destroy()
    def init(self, flist):
        top.bind("<Escape>", lambda:self.close())

这里,lambda不是来自参数,而是来自上下文。

答案 5 :(得分:1)

在Lua和Python中,当“只是编码”时,这是很自然的事情,因为当你引用不是参数的东西时,你就会关闭它。 (因此,大多数这些都是非常沉闷的例子。)

至于具体情况,想象一下撤销/重做系统,其中步骤是(undo(),redo())闭包对。更麻烦的方法可能是:(a)使unredoable类有一个特殊的方法,通用dorky参数,或(b)UnreDoOperation子类无数次。

另一个具体示例是无限列表:您可以使用检索下一个元素的函数来代替使用通用化容器。 (这是迭代器的强大功能的一部分。)在这种情况下,您可以保持一点状态(下一个整数,对于所有非负整数或类似的列表)或对一个位置的引用实际的容器。无论哪种方式,它都是一个引用外部事物的函数。 (在无限列表的情况下,状态变量必须是闭包变量,否则它们对每次调用都是干净的)

答案 6 :(得分:1)

我被告知在haskell中有更多的用途,但我只有在javascript中使用闭包的乐趣,而在javascript中我没有太多看到这一点。我的第一直觉是尖叫“哦不,不再”,实现必须使实现闭包的混乱。 在我读到有关如何实现闭包之后(无论如何都是javascript),现在对我来说似乎并不那么糟糕,至少对我来说,实现看起来有些优雅。

但是从那以后我意识到“封闭”并不是描述这个概念的最佳词汇。我认为最好将它命名为“飞行范围”。

答案 7 :(得分:1)

正如之前的答案之一所指出的那样,你经常会发现自己在使用它们时却几乎没有注意到它们。

一个典型的例子是它们非常常用于设置UI事件处理以获得代码重用,同时仍然允许访问UI上下文。这是一个如何为click事件定义匿名处理函数创建一个包含button函数的colorsetColor()参数的闭包的示例:

function setColor(button, color) {

        button.addEventListener("click", function()
        {
            button.style.backgroundColor = color;
        }, false);
}

window.onload = function() {
        setColor(document.getElementById("StartButton"), "green");
        setColor(document.getElementById("StopButton"), "red");
}

注意:为了准确起见,值得注意的是,在setColor()函数退出之前,实际上并未创建闭包。

答案 8 :(得分:0)

本文包含两个闭包实际有用的示例: Closure