2008-11-02 21 views
36

我看過封閉樣本 - What is a 'Closure'?何時使用閉包?

任何人都可以提供何時使用閉包的簡單示例?

具體地,場景中,封閉件是有道理?

讓我們假設語言沒有關閉支持,那麼還會如何實現類似的事情呢?

不要冒犯任何人,請用c#,python,javascript,ruby等語言發佈代碼示例。
對不起,我還不懂功能語言。

+0

我想你可以看看下面的關於設計模式的鏈接,這些鏈接使我們能夠以簡潔的方式實現[關閉設計模式的帖子](http://arturoherrero.com/2012/04/25/closure -design-patterns /) – Sudarshan 2012-10-13 19:23:03

+0

當這樣的問題關閉時,社區很不幸。這是一個非常合理的問題,許多學習閉包 - 一個困難的話題 - 將會有一個。瞭解真實世界的用例是加速某個難題的學習曲線的好方法。謝天謝地,在關閉之前有很多很好的答案。 – Matt 2017-05-26 17:17:19

回答

30

閉合是非常好的工具。何時使用它們?任何時候你都喜歡......如前所述,另一種選擇是寫一個班;例如,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); 
      }); 
     } 

這可能不是打印你所期望的,因爲變量我被用於每個。你可以看到重複的任何組合 - 甚至10 10。您需要在C#中仔細確定捕獲的變量的範圍:

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

這裏每個j分別捕獲(即不同的編譯器生成的類實例)。

喬恩Skeet有一個很好的博客條目涵蓋C#和java關閉here;或更詳細的信息,請參閱他的書C# in Depth,其中有一整章。

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)內無縫地引用該環境。如果沒有關閉,你必須明確這個環境。

這應該解釋給你什麼時候你會使用閉包:所有的時間。大多數情況下,一個類被實例化來從一個計算的另一部分攜帶一些狀態並將其應用到其他地方,這些實例被支持它們的語言中的閉包優雅地替代。

可以實現一個閉包的對象系統。

+0

感謝您的回答。你能描述一個代碼示例,它有效使用閉包嗎? – shahkalpesh 2008-11-02 08:07:15

1

在Lua和Python的它做的時候「只是編碼」很自然的事情,因爲你引用的東西,這不是一個參數的那一刻,你正在做一個封閉。 (因此大多數情況會比較沉悶)

至於具體的情況,想象一個撤銷/重做系統,其中步驟是(undo(),redo())關閉對的步驟。這樣做的更麻煩的方法可能是:(a)使不可恢復的類有一個特殊的方法,使用通用的dorky參數,或(b)UnReDoOperation的子類超過次數。

另一個具體的例子是無限列表:不使用泛化容器,而是使用檢索下一個元素的函數。 (這是迭代器功能的一部分)。在這種情況下,您可以只保留一點狀態(下一個整數,對於非負整數列表或類似的列表)或對一個位置的引用實際容器。無論哪種方式,這是一個引用自身之外的東西的函數。(在無限列表的情況下,狀態變量必須是閉包變量,否則它們在每次調用時都是乾淨的)

+0

感謝您的回答。你能描述一個代碼示例,它有效使用閉包嗎? – shahkalpesh 2008-11-02 08:06:41

22

我同意「所有時間」的上一個答案。當你使用函數式語言或任何lambdas和closures常見的語言進行編程時,你甚至不會注意到它們。這就像問「功能的場景是什麼?」或者「循環的場景是什麼?」這並不是爲了讓原始問題聽起來很愚蠢,而是要指出,有些語言中的結構沒有按照特定的方案進行定義。你一直都在使用它們,對於一切,這是第二性質。

這讓人想起的:

上人QC娜走 與他的學生,安東。希望 提示大師進入討論, 安東說:「師傅,我聽說 對象是非常好的東西 - 是 這是真的嗎?」 Qc Na看着他的學生 憐憫地回答,「愚蠢的 學生 - 對象只是一個窮人 人的關閉。」

責罵,安東從 他的主人離開他的主人,並返回到他的手機, 意圖在學習關閉。他仔細閱讀了整個「Lambda: Ultimate ...」系列論文及其 表兄弟,並實現了一個具有 關係的對象系統的小型 。他 瞭解很多,並期待 通知他的主人他的進步。

他與QC娜,安東 旁邊的步行試圖打動他的主人通過 說:「主人,我有努力 研究了這個問題,現在明白 的對象是一個真正的窮人的 倒閉。」 Qc Na迴應,用他的棍子擊中了 Anton,並說:「你什麼時候會學習 ?閉包是一個窮人 男人的對象。」那一刻,安東 成爲開悟。

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

+0

不錯。 由於函數式編程是不同的(就管理狀態而言),使用閉包很自然。 我沒有太多的功能編程背景。我使用c#,它引入了這個概念。一個例子仍然值得讚賞。 – shahkalpesh 2008-11-02 08:11:15

14

使用閉包的最簡單的例子是在一些所謂的鑽營。基本上,我們假設我們有一個函數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

這是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中對閉包的支持,所以需要兩個額外的默認參數來傳遞convert和join到遞歸調用中。隨着關閉,該讀取

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) 

在面向對象的語言,你通常不使用閉過於頻繁,因爲你可以使用對象來傳遞狀態 - 和綁定方法,當你的語言有它們。當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是一個無參數的回調被按下時逃逸調用。但是,緊密實現確實需要參數 - 即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()) 

這裏,拉姆達會得到「自我」不是從一個參數,但是從上下文。

0

本文包含的閉包哪裏是真正有用的兩個例子: Closure

1

有人告訴我有在Haskell更多的用途,但我只有在JavaScript中使用閉包的樂趣,並在javascript我不太瞭解這一點。我的第一個直覺就是尖叫「哦,不要,再一次」,實施關閉工作的實施必須是一團糟。 當我閱讀了關於如何實現閉包(以JavaScript的方式)之後,對我來說現在看起來並不那麼糟糕,並且對我來說實現看起來有些優雅。

但是,從那我意識到「封閉」並不是真正描述這個概念的最好詞彙。我認爲應該更好地命名爲「飛行範圍」。

1

作爲以前的答案筆記之一,你經常發現自己在使用它們時幾乎沒有注意到你是。

一個很好的例子是,它們通常用於設置UI事件處理以獲得代碼重用,同時仍允許訪問UI上下文。以下是如何定義一個click事件的匿名處理函數的例子創建一個包含setColor()功能的buttoncolor參數封閉:

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()函數退出。