Domanda

Ho visto esempi di chiusura da - Che cos'è una "chiusura"?

Qualcuno può fornire un semplice esempio di quando usare la chiusura?

In particolare, gli scenari in cui ha senso la chiusura?

Supponiamo che la lingua non abbia il supporto di chiusura, come si potrebbe ottenere ancora qualcosa di simile?

Per non offendere nessuno, pubblica esempi di codice in una lingua come c #, python, javascript, ruby ??ecc.
Mi dispiace, non capisco ancora i linguaggi funzionali.

È stato utile?

Soluzione

Le chiusure sono semplicemente ottimi strumenti. Quando usarli? Ogni volta che ti piace ... Come è già stato detto, l'alternativa è scrivere una lezione; per esempio, prima di C # 2.0, la creazione di un thread con parametri era una vera lotta. Con C # 2.0 non hai nemmeno bisogno del `ParameterizedThreadStart 'che fai semplicemente:

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

Confrontalo con la creazione di una classe con un nome e un valore

O allo stesso modo con la ricerca di un elenco (usando questa volta una lambda):

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

Ancora una volta: l'alternativa sarebbe scrivere una classe con due proprietà e un metodo:

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);

Questo è abbastanza paragonabile a come il compilatore lo fa sotto il cofano (in realtà, utilizza campi di lettura / scrittura pubblici, non di sola lettura in privato).

Il più grande avvertimento con le acquisizioni C # è guardare l'ambito; ad esempio:

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

Questo potrebbe non stampare ciò che ti aspetti, poiché la variabile i viene utilizzata per ciascuno. Potresti vedere qualsiasi combinazione di ripetizioni - anche 10 10. Devi esaminare attentamente le variabili acquisite in C #:

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

Qui ogni j viene catturato separatamente (cioè una diversa istanza di classe generata dal compilatore).

Jon Skeet ha un buon post sul blog che copre le chiusure C # e java qui ; o per maggiori dettagli, vedi il suo libro C # in profondità , che contiene un intero capitolo su di essi.

Altri suggerimenti

Sono d'accordo con una precedente risposta di "sempre". Quando programmi in un linguaggio funzionale o in qualsiasi lingua in cui lambda e chiusure sono comuni, li usi senza nemmeno accorgertene. È come chiedere a " qual è lo scenario di una funzione? & Quot; o " qual è lo scenario per un loop? " Questo non per far sembrare stupida la domanda originale, ma piuttosto per sottolineare che ci sono costrutti in linguaggi che non definisci in termini di scenari specifici. Li usi sempre, per tutto, è una seconda natura.

Questo in qualche modo ricorda:

  

Il venerabile maestro Qc Na stava camminando   con il suo studente, Anton. Sperando di   spingere il maestro in una discussione,   Anton disse: "Maestro, l'ho sentito   gli oggetti sono un'ottima cosa - lo è   questo vero? " Qc Na guardò pietosamente   suo studente e ha risposto, "quot Foolish   allievo: gli oggetti sono semplicemente poveri   chiusure dell'uomo. "

     

Castigato, Anton si congedò   il suo padrone e tornò nella sua cella,   intento a studiare chiusure. lui   leggi attentamente l'intero " Lambda: The   Ultimate ... " serie di articoli e relativi   cugini, e implementato un piccolo   Interprete dello schema con a   sistema di oggetti basato sulla chiusura. lui   imparato molto e non vedevo l'ora   informare il suo padrone dei suoi progressi.

     

Nella sua prossima passeggiata con Qc Na, Anton   ha tentato di impressionare il suo maestro   dicendo " Maestra, ho diligentemente   ha studiato la questione e ora capisco   che gli oggetti sono veramente dei poveri   . Chiusure " Qc Na ha risposto colpendo   Anton con il suo bastone, dicendo " Quando   imparerai? Le chiusure sono povere   oggetto dell'uomo. " In quel momento, Anton   divenne illuminato.

( http://people.csail.mit .edu / Gregs / LL1-discuss-archivio-html / msg03277.html )

L'esempio più semplice di utilizzo delle chiusure è in qualcosa chiamato curry. In sostanza, supponiamo di avere una funzione f () che, quando viene chiamata con due argomenti a e b , li somma insieme. Quindi, in Python, abbiamo:

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

Ma diciamo, per ragioni di argomento, che vogliamo solo chiamare f () con un argomento alla volta. Quindi, invece di f (2, 3) , vogliamo f (2) (3) . Questo può essere fatto in questo modo:

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()

Ora, quando chiamiamo f (2) , otteniamo una nuova funzione, g () ; questa nuova funzione porta con sé variabili dal scope di f () , e così si dice che chiuda quelle variabili, da cui il termine chiusura. Quando chiamiamo g (3) , alla variabile a (che è vincolata dalla definizione di f ) si accede da g ( ) , restituendo 2 + 3 = > 5

Questo è utile in diversi scenari. Ad esempio, se avessi una funzione che accettasse un gran numero di argomenti, ma solo alcuni di essi mi fossero utili, avrei potuto scrivere una funzione generica in questo modo:

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 è ora una funzione che richiede solo 3 argomenti, invece di 9. Evito di dovermi ripetere e ho anche creato una soluzione generica ; se scrivo un'altra funzione con molti argomenti, posso usare di nuovo lo strumento curry .

In genere, se non si hanno chiusure, è necessario definire una classe per portare con sé l'equivalente dell'ambiente di chiusura e passarlo in giro.

Ad esempio, in un linguaggio come Lisp, si può definire una funzione che restituisce una funzione (con un ambiente chiuso) per aggiungere in tal modo una quantità predefinita al suo argomento:

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

e usalo in questo modo:

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

In una lingua senza chiusure, faresti qualcosa del genere:

public class Adder {
  private int howMuch;

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

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

e quindi usalo in questo modo:

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

La chiusura comporta implicitamente il suo ambiente; ti riferisci perfettamente a quell'ambiente dall'interno della parte in esecuzione (lambda). Senza chiusure è necessario rendere esplicito quell'ambiente.

Questo dovrebbe spiegarti quando useresti chiusure: sempre . La maggior parte dei casi in cui una classe viene istanziata per portare con sé un po 'di stato da un'altra parte del calcolo e applicarla altrove viene elegantemente sostituita da chiusure in lingue che le supportano.

Si può implementare un sistema a oggetti con chiusure.

Ecco un esempio dalla libreria standard di Python, inspect.py. Attualmente legge

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)

Questo ha, come parametri, una funzione di conversione e una funzione di join, e ricorsivamente ricopre liste e tuple. La ricorsione è implementata usando map (), dove il primo parametro è una funzione. Il codice precede il supporto per le chiusure in Python, quindi ha bisogno di due argomenti predefiniti aggiuntivi, per passare la conversione e unirsi alla chiamata ricorsiva. Con le chiusure, si legge

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)

Nelle lingue OO, in genere non si utilizzano le chiusure troppo spesso, poiché è possibile utilizzare gli oggetti per passare lo stato e i metodi associati, quando la lingua li possiede. Quando Python non aveva chiusure, la gente diceva che Python emula chiusure con oggetti, mentre Lisp emula oggetti con chiusure. Ad esempio da 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)

Qui self.close è un callback senza parametri richiamato quando si preme Esc. Tuttavia, l'implementazione vicina necessita di parametri - vale a dire self, e quindi self.top, self.node. Se Python non avesse metodi associati, potresti scrivere

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

Qui, la lambda otterrebbe " self " non da un parametro, ma dal contesto.

In Lua e Python è una cosa molto naturale da fare quando si "codifica", perché nel momento in cui si fa riferimento a qualcosa che non è un parametro, si sta chiudendo. (quindi molti di questi saranno piuttosto noiosi come esempi.)

Per quanto riguarda un caso concreto, immagina un sistema undo / redo, in cui i passaggi sono coppie di chiusure (undo (), redo ()). I modi più ingombranti di fare ciò potrebbero essere: (a) Rendere le classi non ripetibili hanno un metodo speciale con argomenti universalmente insensati, o (b) sottoclasse UnReDoOperation umpteen volte.

Un altro esempio concreto sono gli elenchi infiniti: invece di lavorare con contenitori generici, capovolgi una funzione che recupera l'elemento successivo. (fa parte del potere degli iteratori.) In questo caso puoi mantenere solo un po 'di stato (il numero intero successivo, per l'elenco di numeri interi non negativi o simili) o un riferimento a una posizione in un contenitore reale. Ad ogni modo, è una funzione che fa riferimento a qualcosa che è fuori di sé. (nel caso dell'elenco infinito, le variabili di stato devono essere variabili di chiusura, perché altrimenti sarebbero pulite per ogni chiamata)

Mi è stato detto che ci sono più usi in haskell, ma ho avuto solo il piacere di usare chiusure in javascript, e in javascript non vedo molto il punto. Il mio primo istinto fu quello di urlare "oh no, non ancora" in quale casino l'implementazione deve essere per far funzionare le chiusure Dopo aver letto di come sono state implementate le chiusure (comunque in javascript), non mi sembra così male adesso e l'implementazione sembra in qualche modo elegante, almeno per me.

Ma da ciò ho capito "chiusura" non è proprio la parola migliore per descrivere il concetto. Penso che dovrebbe essere chiamato "ambito di volo". & Quot;

Come una delle note precedenti sulle risposte, ti ritrovi spesso ad usarli senza accorgerti che lo sei.

Un esempio è il fatto che sono molto comunemente usati nell'impostazione della gestione degli eventi dell'interfaccia utente per ottenere il riutilizzo del codice pur consentendo l'accesso al contesto dell'interfaccia utente. Ecco un esempio di come la definizione di una funzione di gestore anonimo per un evento click crea una chiusura che include i parametri button e color di setColor () funzione:

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");
}

Nota: per precisione, vale la pena notare che la chiusura non viene effettivamente creata fino alla chiusura della funzione setColor () .

Questo articolo include due esempi di dove le chiusure sono effettivamente utili: Chiusura

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top