Perché un programma dovrebbe usare una chiusura?


58

Dopo aver letto molti post che spiegano le chiusure qui, mi manca ancora un concetto chiave: perché scrivere una chiusura? Quale compito specifico eseguirà un programmatore che potrebbe essere meglio servito da una chiusura?

Esempi di chiusure in Swift sono gli accessi di un NSUrl e l'uso del geocoder inverso. Ecco un esempio. Sfortunatamente, quei corsi presentano solo la chiusura; non spiegano perché la soluzione del codice sia scritta come una chiusura.

Un esempio di un problema di programmazione nel mondo reale che potrebbe innescare il mio cervello nel dire "aha, dovrei scrivere una chiusura per questo", sarebbe più informativo di una discussione teorica. Non mancano le discussioni teoriche disponibili su questo sito.


7
"Eslanazione delle chiusure recentemente recensita qui" - ti manca un link?
Dan Pichelman,

2
Dovresti inserire questo chiarimento sull'attività asincrona in un commento, sotto la risposta pertinente. Non fa parte della domanda originale e il risponditore non verrà mai informato della modifica.
Robert Harvey,

5
Solo un bocconcino: il punto cruciale di Closures è lo scoping lessicale (al contrario dello scoping dinamico), le chiusure senza scoping lessicale sono un po 'inutili. Le chiusure sono talvolta chiamate chiusure lessicali.
Hoffmann,

1
Le chiusure consentono di creare istanze di funzioni .
user253751

1
Leggi qualsiasi buon libro sulla programmazione funzionale. Forse inizia con SICP
Basile Starynkevitch il

Risposte:


33

Prima di tutto, non c'è nulla di impossibile senza l'utilizzo di chiusure. Puoi sempre sostituire una chiusura con un oggetto che implementa un'interfaccia specifica. È solo una questione di brevità e riduzione dell'accoppiamento.

In secondo luogo, tenere presente che le chiusure vengono spesso utilizzate in modo inappropriato, in cui un semplice riferimento di funzione o un altro costrutto sarebbero più chiari. Non dovresti prendere ogni esempio che vedi come una buona pratica.

Il punto in cui le chiusure brillano su altri costrutti è quando si utilizzano funzioni di ordine superiore, quando è effettivamente necessario comunicare lo stato e è possibile renderlo univoco, come in questo esempio JavaScript dalla pagina di Wikipedia sulle chiusure :

// Return a list of all books with at least 'threshold' copies sold.
function bestSellingBooks(threshold) {
  return bookList.filter(
      function (book) { return book.sales >= threshold; }
    );
}

Qui, thresholdviene comunicato in modo molto succinto e naturale da dove viene definito a dove viene utilizzato. Il suo ambito di applicazione è precisamente limitato il più piccolo possibile. filternon deve essere scritto per consentire la possibilità di passare dati definiti dal cliente come una soglia. Non dobbiamo definire alcuna struttura intermedia al solo scopo di comunicare la soglia in questa piccola funzione. È completamente autonomo.

È possibile scrivere questo, senza una chiusura, ma richiederà molto più codice, e più difficile da seguire. Inoltre, JavaScript ha una sintassi lambda abbastanza dettagliata. In Scala, ad esempio, l'intero corpo della funzione sarebbe:

bookList filter (_.sales >= threshold)

Se puoi comunque usare ECMAScript 6 , grazie alle funzioni fat fat arrow anche il codice JavaScript diventa molto più semplice e può essere effettivamente inserito su una sola riga.

const bestSellingBooks = (threshold) => bookList.filter(book => book.sales >= threshold);

Nel tuo codice, cerca i luoghi in cui generi molta piastra della caldaia solo per comunicare valori temporanei da un posto all'altro. Queste sono eccellenti opportunità per considerare la sostituzione con una chiusura.


In che modo esattamente le chiusure riducono l'accoppiamento?
sbichenko,

Senza chiusure, è necessario imporre restrizioni sia al bestSellingBookscodice che al filtercodice, come un'interfaccia specifica o un argomento relativo ai dati utente, per poter comunicare i thresholddati. Ciò collega le due funzioni in modi molto meno riutilizzabili.
Karl Bielefeldt,

51

A titolo di spiegazione, prenderò in prestito un po 'di codice da questo eccellente post sul blog sulle chiusure . È JavaScript, ma è la lingua utilizzata dalla maggior parte dei post di blog che parlano di chiusure, poiché le chiusure sono così importanti in JavaScript.

Diciamo che volevi renderizzare un array come una tabella HTML. Potresti farlo in questo modo:

function renderArrayAsHtmlTable (array) {
  var table = "<table>";
  for (var idx in array) {
    var object = array[idx];
    table += "<tr><td>" + object + "</td></tr>";
  }
  table += "</table>";
  return table;
}

Ma sei in balia di JavaScript su come verrà reso ogni elemento dell'array. Se si desidera controllare il rendering, è possibile farlo:

function renderArrayAsHtmlTable (array, renderer) {
  var table = "<table>";
  for (var idx in array) {
    var object = array[idx];
    table += "<tr><td>" + renderer(object) + "</td></tr>";
  }
  table += "</table>";
  return table;
}

E ora puoi semplicemente passare una funzione che restituisce il rendering desiderato.

Che cosa succede se si desidera visualizzare un totale parziale in ogni riga della tabella? Avresti bisogno di una variabile per tracciare quel totale, vero? Una chiusura consente di scrivere una funzione di rendering che si chiude sulla variabile totale in esecuzione e consente di scrivere un renderer in grado di tenere traccia del totale parziale:

function intTableWithTotals (intArray) {
  var total = 0;
  var renderInt = function (i) {
    total += i;
    return "Int: " + i + ", running total: " + total;
  };
  return renderObjectsInTable(intArray, renderInt);
}

La magia che sta accadendo qui è che renderInt mantiene l'accesso alla totalvariabile, anche se renderIntviene ripetutamente chiamato ed esce.

In un linguaggio più tradizionalmente orientato agli oggetti rispetto a JavaScript, potresti scrivere una classe che contiene questa variabile totale e passarla invece di creare una chiusura. Ma una chiusura è un modo molto più potente, pulito ed elegante per farlo.

Ulteriori letture


10
In generale, puoi dire che "funzione di prima classe == oggetto con un solo metodo", "Chiusura == oggetto con un solo metodo e stato", "oggetto == fascio di chiusure".
Jörg W Mittag,

Sembra buono. Un'altra cosa, e questo potrebbe essere la pedanteria, è che Javascript sia orientato agli oggetti, e si potrebbe creare un oggetto che contiene la variabile totale e passare che in giro. Le chiusure rimangono molto più potenti, pulite ed eleganti e rendono anche Javascript molto più idiomatico, ma il modo in cui è scritto potrebbe implicare che Javascript non può farlo in modo orientato agli oggetti.
KRyan il

2
In realtà, essere davvero pedanti: le chiusure sono ciò che rende JavaScript orientato agli oggetti in primo luogo! OO riguarda l'astrazione dei dati e le chiusure sono il modo per eseguire l'astrazione dei dati in JavaScript.
Jörg W Mittag,

@ JörgWMittag: Come puoi vedere i cloures come oggetti con un solo metodo, puoi vedere gli oggetti come una raccolta di chiusure che si chiudono sulle stesse variabili: le variabili membro di un oggetto sono solo le variabili locali del costruttore dell'oggetto e i metodi dell'oggetto sono chiusure definite nell'ambito del costruttore e rese disponibili per essere chiamate in seguito. La funzione di costruzione restituisce una funzione di ordine superiore (l'oggetto) che può essere inviata su ogni chiusura in base al nome del metodo utilizzato per l'invocazione.
Giorgio,

@Giorgio: In effetti, è così che gli oggetti sono tipicamente implementati in Scheme, per esempio, ed è in effetti anche il modo in cui gli oggetti sono implementati in JavaScript (non sorprende considerando la sua stretta relazione con Scheme, dopo tutto, Brendan Eich è stato inizialmente assunto per progettare un Dialetto di Scheme e implementazione di un interprete Scheme incorporato all'interno di Netscape Navigator, e solo in seguito gli è stato ordinato di creare un linguaggio "con oggetti che assomigliano a C ++", dopo di che ha apportato la quantità minima assoluta di modifiche per soddisfare tali requisiti di marketing).
Jörg W Mittag,

23

Lo scopo di closuresè semplicemente quello di preservare lo stato; da qui il nome closure: si chiude sullo stato. Per facilitare ulteriori spiegazioni, userò Javascript.

In genere hai una funzione

function sayHello(){
    var txt="Hello";
    return txt;
}

dove l'ambito delle variabili è associato a questa funzione. Quindi dopo l'esecuzione la variabile non txtrientra nell'ambito. Non è possibile accedervi o utilizzarlo al termine dell'esecuzione della funzione.

Le chiusure sono costrutti linguistici, che consentono - come detto prima - di preservare lo stato delle variabili e prolungare così l'ambito di applicazione.

Questo potrebbe essere utile in diversi casi. Un caso d'uso è la costruzione di funzioni di ordine superiore .

In matematica e informatica, una funzione di ordine superiore (anche forma funzionale, funzionale o functor) è una funzione che svolge almeno una delle seguenti operazioni: 1

  • accetta una o più funzioni come input
  • genera una funzione

Un esempio semplice, ma certamente non troppo utile è:

 makeadder=function(a){
     return function(b){
         return a+b;
     }
 }

 add5=makeadder(5);
 console.log(add5(10)); 

Si definisce una funzione makedadder, che accetta un parametro come input e restituisce una funzione . Esiste una funzione esternafunction(a){} e una interna. function(b){}{} Inoltre definisci (implicitamente) un'altra funzione add5come risultato della chiamata alla funzione di ordine superiore makeadder. makeadder(5)restituisce una funzione anonima ( interna ), che a sua volta accetta 1 parametro e restituisce la somma del parametro della funzione esterna e del parametro della funzione interna .

Il trucco è che mentre si restituisce la funzione interna , che fa l'effettiva aggiunta, aviene preservato l'ambito del parametro della funzione esterna ( ). add5 ricorda che il parametro aera 5.

O per mostrare almeno un esempio in qualche modo utile:

  makeTag=function(openTag, closeTag){
     return function(content){
         return openTag +content +closeTag;
     }
 }

 table=makeTag("<table>","</table>")
 tr=makeTag("<tr>", "</tr>");
 td=makeTag("<td>","</td>");
 console.log(table(tr(td("I am a Row"))));

Un altro caso d'uso comune è il cosiddetto IIFE = espressione della funzione immediatamente invocata. È molto comune in javascript falsificare le variabili dei membri privati. Ciò avviene tramite una funzione, che crea un ambito privato = closure, poiché viene richiamata immediatamente dopo la definizione. La struttura è function(){}(). Notare le parentesi ()dopo la definizione. Ciò rende possibile usarlo per la creazione di oggetti con un modello di modulo rivelatore . Il trucco sta nel creare un ambito e restituire un oggetto, che ha accesso a questo ambito dopo l'esecuzione dell'IIFE.

L'esempio di Addi è simile al seguente:

 var myRevealingModule = (function () {

         var privateVar = "Ben Cherry",
             publicVar = "Hey there!";

         function privateFunction() {
             console.log( "Name:" + privateVar );
         }

         function publicSetName( strName ) {
             privateVar = strName;
         }

         function publicGetName() {
             privateFunction();
         }


         // Reveal public pointers to
         // private functions and properties

         return {
             setName: publicSetName,
             greeting: publicVar,
             getName: publicGetName
         };

     })();

 myRevealingModule.setName( "Paul Kinlan" );

L'oggetto restituito ha riferimenti a funzioni (ad esempio publicSetName), che a loro volta hanno accesso a variabili "private" privateVar.

Ma questi sono casi d'uso più speciali per Javascript.

Quale compito specifico potrebbe svolgere un programmatore che potrebbe essere meglio servito da una chiusura?

Ci sono diverse ragioni per questo. Uno potrebbe essere, che è naturale per lui, dal momento che segue un paradigma funzionale . O in Javascript: è pura necessità fare affidamento su chiusure per aggirare alcune stranezze del linguaggio.


"Lo scopo delle chiusure è semplicemente quello di preservare lo stato; da qui il nome di chiusura - si chiude sullo stato": dipende davvero dalla lingua. Le chiusure si chiudono su nomi / variabili esterni. Questi possono indicare lo stato (posizioni di memoria che possono essere modificate) ma possono anche indicare valori (immutabili). Le chiusure in Haskell non conservano lo stato: conservano le informazioni che erano conosciute al momento e nel contesto in cui è stata creata la chiusura.
Giorgio,

1
»Conservano le informazioni che erano conosciute al momento e nel contesto in cui è stata creata la chiusura« indipendentemente dal fatto che possano essere cambiate o meno, è stato - forse stato immutabile .
Thomas Junk,

16

Esistono due casi d'uso principali per le chiusure:

  1. Asincronia. Supponiamo che tu voglia eseguire un'attività che richiederà del tempo, quindi fare qualcosa al termine. Puoi far aspettare che il tuo codice sia completato, il che blocca ulteriori esecuzioni e può non rispondere al tuo programma, oppure chiamare il tuo compito in modo asincrono e dire "inizia questa lunga attività in background, e quando completa, esegui questa chiusura", dove la chiusura contiene il codice da eseguire al termine.

  2. Callback. Questi sono anche noti come "delegati" o "gestori di eventi" a seconda della lingua e della piattaforma. L'idea è che hai un oggetto personalizzabile che, in determinati punti ben definiti, eseguirà un evento , che esegue una chiusura passata dal codice che lo imposta. Ad esempio, nell'interfaccia utente del tuo programma potresti avere un pulsante e assegnargli una chiusura che contiene il codice da eseguire quando l'utente fa clic sul pulsante.

Ci sono molti altri usi per chiusure, ma quelli sono i due principali.


23
Quindi, fondamentalmente callbacks allora, dal momento che il primo esempio è anche un callback.
Robert Harvey,

2
@RobertHarvey: tecnicamente vero, ma sono diversi modelli mentali. Ad esempio, (in generale,) ti aspetti che il gestore eventi venga chiamato più volte, ma la tua continuazione asincrona verrà chiamata una sola volta. Ma sì, tecnicamente tutto ciò che faresti con una chiusura è una richiamata. (A meno che non lo stia convertendo in un albero delle espressioni, ma questa è una questione completamente diversa.);)
Mason Wheeler,

4
@RobertHarvey: visualizzare chiusure come callback ti mette in uno stato d'animo che ti impedirà davvero di usarle in modo efficace. Le chiusure sono callback su steroidi.
gnasher729,

13
@MasonWheeler Detto in questo modo, sembra sbagliato. I callback non hanno nulla a che fare con le chiusure; naturalmente è possibile richiamare una funzione all'interno di una chiusura o chiamare una chiusura come richiamata; ma una richiamata non è necessaria una chiusura. Una richiamata è solo una semplice chiamata di funzione. Un eventhandler non è di per sé una chiusura. Un delegato non ha bisogno di una chiusura: è principalmente il modo C # di puntatori a funzioni. Ovviamente potresti usarlo per costruire una chiusura. La tua spiegazione è imprecisa. Il punto sta chiudendo sullo stato e sfruttandolo.
Thomas Junk,

5
Forse ci sono connotazioni specifiche di JS in corso qui che mi stanno facendo fraintendere, ma questa risposta mi sembra assolutamente sbagliata. Asincronia o callback possono usare tutto ciò che è "richiamabile". Non è richiesta una chiusura per nessuno dei due: una normale funzione andrà bene. Nel frattempo, una chiusura, come definita nella programmazione funzionale, o come usata in Python, è utile, come dice Thomas, perché "chiude" un certo stato, cioè una variabile, e dà una funzione in un ambito interno accesso coerente a quella variabile valore anche se la funzione viene chiamata ed esce molte volte.
Jonathan Hartley,

13

Un paio di altri esempi:

Ordinamento
La maggior parte delle funzioni di ordinamento operano confrontando coppie di oggetti. Sono necessarie alcune tecniche di confronto. Limitare il confronto a un operatore specifico significa un ordinamento piuttosto inflessibile. Un approccio molto migliore consiste nel ricevere una funzione di confronto come argomento della funzione di ordinamento. A volte una funzione di confronto senza stato funziona correttamente (ad esempio, l'ordinamento di un elenco di numeri o nomi), ma cosa succede se il confronto necessita di stato?

Ad esempio, considera di ordinare un elenco di città per distanza in base a una posizione specifica. Una brutta soluzione è quella di memorizzare le coordinate di quella posizione in una variabile globale. Questo rende la funzione di confronto stessa apolide, ma a costo di una variabile globale.

Questo approccio impedisce di disporre di più thread che ordinano contemporaneamente lo stesso elenco di città in base alla loro distanza in due posizioni diverse. Una chiusura che racchiude la posizione risolve questo problema e elimina la necessità di una variabile globale.


Numeri casuali
L'originale rand()non ha preso argomenti. I generatori di numeri pseudocasuali hanno bisogno di stato. Alcuni (ad esempio, Mersenne Twister) hanno bisogno di molto stato. Anche lo stato semplice ma terribile rand()necessario. Leggi un giornale di matematica su un nuovo generatore di numeri casuali e vedrai inevitabilmente variabili globali. È bello per gli sviluppatori della tecnica, non così bello per i chiamanti. Incapsulare quello stato in una struttura e passare la struttura al generatore di numeri casuali è un modo per aggirare il problema dei dati globali. Questo è l'approccio utilizzato in molti linguaggi non OO per far rientrare un generatore di numeri casuali. Una chiusura nasconde lo stato del chiamante. Una chiusura offre la semplice sequenza di chiamata rand()e il rientro dello stato incapsulato.

C'è di più nei numeri casuali di un semplice PRNG. La maggior parte delle persone che vogliono casualità lo vogliono distribuito in un certo modo. Inizierò con numeri estratti casualmente tra 0 e 1, o U (0,1) in breve. Qualsiasi PRNG che genera numeri interi compresi tra 0 e un certo numero massimo lo farà; dividere semplicemente (come un punto mobile) il numero intero casuale per il massimo. Un modo conveniente e generico per implementare questo è creare una chiusura che accetta una chiusura (PRNG) e il massimo come input. Ora abbiamo un generatore casuale generico e facile da usare per U (0,1).

Esistono diverse altre distribuzioni oltre a U (0,1). Ad esempio, una distribuzione normale con una certa media e deviazione standard. Ogni normale algoritmo del generatore di distribuzione che ho incontrato utilizza un generatore U (0,1). Un modo conveniente e generico per creare un generatore normale è quello di creare una chiusura che incapsuli il generatore U (0,1), la media e la deviazione standard come stato. Questa è, almeno concettualmente, una chiusura che prende una chiusura che prende una chiusura come argomento.


7

Le chiusure equivalgono agli oggetti che implementano un metodo run () e, al contrario, gli oggetti possono essere emulati con chiusure.

  • Il vantaggio delle chiusure è che possono essere utilizzate facilmente ovunque ci si aspetti una funzione: ovvero funzioni di ordine superiore, semplici callback (o Pattern di strategia). Non è necessario definire un'interfaccia / classe per creare chiusure ad hoc.

  • Il vantaggio degli oggetti è la possibilità di avere interazioni più complesse: più metodi e / o interfacce diverse.

Quindi, l'uso della chiusura o degli oggetti è principalmente una questione di stile. Ecco un esempio di cose che rendono facili le chiusure ma è scomodo da implementare con gli oggetti:

 (let ((seen))
    (defun register-name (name)
       (pushnew name seen :test #'string=))

    (defun all-names ()
       (copy-seq seen))

    (defun reset-name-registry ()
       (setf seen nil)))

Fondamentalmente, incapsuli uno stato nascosto a cui si accede solo attraverso chiusure globali: non è necessario fare riferimento a nessun oggetto, utilizzare solo il protocollo definito dalle tre funzioni.


  • Sto estendendo la risposta per rispondere a questo commento da supercat *.

Mi fido del primo commento di Supercat sul fatto che in alcune lingue è possibile controllare con precisione la durata degli oggetti, mentre la stessa cosa non è vera per le chiusure. Nel caso di linguaggi raccolti in modo inutile, tuttavia, la durata degli oggetti è generalmente illimitata ed è quindi possibile costruire una chiusura che potrebbe essere chiamata in un contesto dinamico in cui non dovrebbe essere chiamata (lettura da una chiusura dopo un flusso è chiuso, ad esempio).

Tuttavia, è abbastanza semplice prevenire tale uso improprio catturando una variabile di controllo che proteggerà l'esecuzione di una chiusura. Più precisamente, ecco cosa ho in mente (in Common Lisp):

(defun guarded (function)
  (let ((active t))
    (values (lambda (&rest args)
              (when active
                (apply function args)))
            (lambda ()
              (setf active nil)))))

Qui prendiamo un designatore di funzioni functione restituiamo due chiusure, entrambe catturando una variabile locale denominata active:

  • il primo a functioncui activeviene delegato , solo quando è vero
  • il secondo è impostato actionsu nilaka false.

Invece (when active ...), è ovviamente possibile avere (assert active)un'espressione, che potrebbe generare un'eccezione nel caso in cui la chiusura venga chiamata quando non dovrebbe esserlo. Inoltre, tieni presente che il codice non sicuro potrebbe già generare un'eccezione da solo se usato male, quindi raramente hai bisogno di un tale wrapper.

Ecco come lo useresti:

(use-package :metabang-bind) ;; for bind

(defun example (obj1 obj2)
  (bind (((:values f f-deactivator)(guarded (lambda () (do-stuff obj1))))
         ((:values g g-deactivator)(guarded (lambda () (do-thing obj2)))))

    ;; ensure the closure are inactive when we exit
    (unwind-protect
         ;; pass closures to other functions
         (progn
           (do-work f)
           (do-work g))

      ;; cleanup code: deactivate closures
      (funcall f-deactivator)
      (funcall g-deactivator))))

Si noti che le chiusure desattivanti potrebbero essere date anche ad altre funzioni; qui, le activevariabili locali non sono condivise tra fe g; inoltre, oltre a active, fsi riferisce solo obj1e gsi riferisce solo a obj2.

L'altro punto menzionato da Supercat è che le chiusure possono portare a perdite di memoria, ma sfortunatamente, è il caso di quasi tutto in ambienti di immondizia. Se sono disponibili, questo può essere risolto da puntatori deboli (la chiusura stessa potrebbe essere mantenuta in memoria, ma non impedisce la garbage collection di altre risorse).


1
Uno svantaggio delle chiusure comunemente implementate è che una volta che una chiusura che utilizza una delle variabili locali di un metodo viene esposta al mondo esterno, il metodo che definisce la chiusura può perdere il controllo su quando la variabile viene letta e scritta. Non esiste un modo conveniente, nella maggior parte delle lingue, per definire una chiusura effimera, passarla a un metodo e garantire che cesserà di esistere una volta che il metodo che lo riceve è tornato, né esiste alcun modo in linguaggi multi-thread per garantire che una chiusura non venga eseguita in un contesto di threading imprevisto.
supercat

@supercat: dai un'occhiata a Objective-C e Swift.
gnasher729,

1
@supercat Non esisterebbe lo stesso svantaggio degli oggetti con stato?
Andres F.

@AndresF .: È possibile incapsulare un oggetto con stato in un wrapper che supporta l'invalidazione; se il codice incapsula un privato List<T>in una (classe ipotetica) TemporaryMutableListWrapper<T>e lo espone a un codice esterno, si potrebbe garantire che se invalida il wrapper, il codice esterno non avrà più modo di manipolare il List<T>. Si possono progettare chiusure per consentire l'invalidazione una volta raggiunto il loro scopo previsto, ma non è conveniente. Esistono delle chiusure per rendere certi determinati schemi e lo sforzo richiesto per proteggerli lo negherebbe.
supercat

1
@coredump: in C #, se due chiusure hanno delle variabili in comune, lo stesso oggetto generato dal compilatore servirà entrambi, poiché le modifiche apportate a una variabile condivisa da una chiusura devono essere viste dall'altra. Evitare tale condivisione avrebbe richiesto che ogni chiusura fosse il proprio oggetto che contenesse le proprie variabili non condivise, nonché un riferimento a un oggetto condiviso che contenesse le variabili condivise. Non impossibile, ma avrebbe aggiunto un ulteriore livello di dereferenziamento alla maggior parte degli accessi variabili, rallentando tutto.
supercat,

6

Nulla che non sia già stato detto, ma forse un esempio più semplice.

Ecco un esempio JavaScript che utilizza i timeout:

// Example function that logs something to the browser's console after a given delay
function delayedLog(message, delay) {
  // this function will be called when the timer runs out
  var fire = function () {
    console.log(message); // closure magic!
  };

  // set a timeout that'll call fire() after a delay
  setTimeout(fire, delay);
}

Quello che succede qui è che quando delayedLog()viene chiamato, ritorna immediatamente dopo aver impostato il timeout e il timeout continua a scorrere in background.

Ma quando il timeout scade e chiama la fire()funzione, la console visualizzerà messageciò a cui era stato originariamente passato delayedLog(), perché è ancora disponibile fire()tramite chiusura. Puoi chiamare delayedLog()quanto vuoi, con un messaggio diverso e ritardare ogni volta, e farà la cosa giusta.

Ma immaginiamo che JavaScript non abbia chiusure.

Un modo sarebbe quello di rendere il setTimeout()blocco - più simile a una funzione "sleep" - quindi delayedLog()l'ambito non si spegne fino allo scadere del timeout. Ma bloccare tutto non è molto bello.

Un altro modo sarebbe quello di mettere la messagevariabile in qualche altro ambito che sarà accessibile dopo che delayedLog()l'ambito è andato.

Potresti usare variabili globali - o almeno "più ampie", ma dovresti capire come tenere traccia di quale messaggio va con quale timeout. Ma non può essere solo una coda sequenziale FIFO, perché puoi impostare il ritardo che desideri. Quindi potrebbe essere "first in, third out" o qualcosa del genere. Quindi avresti bisogno di altri mezzi per legare una funzione a tempo alle variabili di cui ha bisogno.

È possibile creare un'istanza di un oggetto timeout che "raggruppa" il timer con il messaggio. Il contesto di un oggetto è più o meno un ambito che rimane. Quindi avresti il ​​timer eseguito nel contesto dell'oggetto, quindi avrebbe accesso al messaggio giusto. Ma dovresti archiviare quell'oggetto perché senza riferimenti verrebbe raccolta spazzatura (senza chiusure, non ci sarebbero riferimenti impliciti ad esso). E dovresti rimuovere l'oggetto una volta sparato il suo timeout, altrimenti rimarrà solo in giro. Quindi avresti bisogno di una sorta di elenco di oggetti di timeout e controllalo periodicamente per gli oggetti "spesi" da rimuovere - o gli oggetti si aggiungerebbero e rimuoveranno dall'elenco e ...

Quindi ... sì, questo sta diventando noioso.

Per fortuna, non è necessario utilizzare un ambito più ampio o contorcersi gli oggetti solo per mantenere determinate variabili. Poiché JavaScript ha delle chiusure, hai già esattamente l'ambito di cui hai bisogno. Un ambito che ti dà accesso alla messagevariabile quando ne hai bisogno. E per questo, puoi cavartela scrivendo delayedLog()come sopra.


Ho un problema a chiamarlo chiusura . Forse vorrei coniare la chiusura accidentale . messageè racchiuso nell'ambito di funzione di firee quindi citato in ulteriori inviti; ma lo fa accidentalmente per così dire. Tecnicamente è una chiusura. +1 comunque;)
Thomas Junk,

@ThomasJunk Non sono sicuro di seguirlo. Come sarebbe una " chiusura non accidentale"? Ho visto il tuo makeadderesempio sopra, che, ai miei occhi, sembra più o meno lo stesso. Restituisce una funzione "al curry" che accetta un singolo arg invece di due; usando gli stessi mezzi, creo una funzione che accetta zero argomenti. Semplicemente non lo restituisco, ma setTimeoutinvece lo passo .
Flambino,

»Semplicemente non lo restituisco« forse, questo è il punto, che fa la differenza per me. Non riesco ad articolare la mia "preoccupazione";) In termini tecnici hai ragione al 100%. Fare riferimento messagea firegenera la chiusura. E quando viene chiamato setTimeoutfa uso dello stato preservato.
Thomas Junk,

1
@ThomasJunk Vedo che potrebbe avere un odore leggermente diverso. Potrei non condividere la tua preoccupazione, però :) O, in ogni caso, non la definirei "accidentale" - abbastanza sicuro di averla codificata in questo modo apposta;)
Flambino,

3

PHP può essere usato per aiutare a mostrare un esempio reale in una lingua diversa.

protected function registerRoutes($dic)
{
  $router = $dic['router'];

  $router->map(['GET','OPTIONS'],'/api/users',function($request,$response) use ($dic)
  {
    $controller = $dic['user_api_controller'];
    return $controller->findAllAction($request,$response);
  })->setName('api_users');
}

Quindi sostanzialmente sto registrando una funzione che verrà eseguita per l' URI / api / users . Questa è in realtà una funzione middleware che finisce per essere memorizzata su uno stack. Altre funzioni saranno racchiuse attorno ad esso. Praticamente come fa Node.js / Express.js .

Il contenitore di iniezione delle dipendenze è disponibile (tramite la clausola use) all'interno della funzione quando viene chiamato. È possibile creare una sorta di classe di azioni di instradamento, ma questo codice risulta essere più semplice, rapido e facile da mantenere.


-1

Una chiusura è un pezzo di codice arbitrario, comprese le variabili, che può essere gestito come dati di prima classe.

Un esempio banale è il buon vecchio qsort: è una funzione per ordinare i dati. Devi dargli un puntatore a una funzione che confronta due oggetti. Quindi devi scrivere una funzione. Potrebbe essere necessario parametrizzare tale funzione, il che significa che si assegnano variabili statiche. Ciò significa che non è thread-safe. Sei in DS. Quindi scrivi un'alternativa che richiede una chiusura anziché un puntatore a funzione. Risolvi immediatamente il problema della parametrizzazione perché i parametri diventano parte della chiusura. Rendi il tuo codice più leggibile perché scrivi come gli oggetti vengono confrontati direttamente con il codice che chiama la funzione di ordinamento.

Ci sono tonnellate di situazioni in cui si desidera eseguire alcune azioni che richiedono una buona dose di codice della piastra della caldaia, oltre a un piccolo ma essenziale pezzo di codice che deve essere adattato. Si evita il codice del boilerplate scrivendo una volta una funzione che accetta un parametro di chiusura e fa tutto il codice del boilerplate al suo interno, quindi è possibile chiamare questa funzione e passare il codice per adattarlo come chiusura. Un modo molto compatto e leggibile per scrivere il codice.

Hai una funzione in cui un codice non banale deve essere eseguito in molte situazioni diverse. Questo produceva la duplicazione del codice o il codice contorto in modo che il codice non banale fosse presente una sola volta. Trivial: assegni una chiusura a una variabile e la chiami nel modo più ovvio ovunque sia necessaria.

Multithreading: iOS / MacOS X ha funzioni per fare cose come "eseguire questa chiusura su un thread in background", "... sul thread principale", "... sul thread principale, tra 10 secondi da adesso". Rende banale il multithreading .

Chiamate asincrone: ecco cosa ha visto l'OP. Qualsiasi chiamata che accede a Internet o qualsiasi altra cosa che potrebbe richiedere del tempo (come la lettura delle coordinate GPS) è qualcosa in cui non si può attendere il risultato. Quindi hai funzioni che fanno le cose in background e poi passi una chiusura per dire loro cosa fare quando sono finite.

È un piccolo inizio. Cinque situazioni in cui le chiusure sono rivoluzionarie in termini di produzione di codice compatto, leggibile, affidabile ed efficiente.


-4

Una chiusura è un modo abbreviato di scrivere un metodo in cui deve essere utilizzato. Ti risparmia lo sforzo di dichiarare e scrivere un metodo separato. È utile quando il metodo verrà utilizzato una sola volta e la definizione del metodo è breve. I vantaggi sono ridotti digitando in quanto non è necessario specificare il nome della funzione, il suo tipo di ritorno o il suo modificatore di accesso. Inoltre, quando leggi il codice non devi cercare altrove la definizione del metodo.

Quanto sopra è un riassunto di Comprendi le espressioni lambda di Dan Avidar.

Ciò ha chiarito l'uso delle chiusure per me perché chiarisce le alternative (chiusura vs. metodo) e i vantaggi di ciascuna.

Il seguente codice viene utilizzato una volta e una volta solo durante l'installazione. Scrivendolo sul posto sotto viewDidLoad evita il problema di cercarlo altrove e accorcia le dimensioni del codice.

myPhoton!.getVariable("Temp", completion: { (result:AnyObject!, error:NSError!) -> Void in
  if let e = error {
    self.getTempLabel.text = "Failed reading temp"
  } else {
    if let res = result as? Float {
    self.getTempLabel.text = "Temperature is \(res) degrees"
    }
  }
})

Inoltre, prevede il completamento di un processo asincrono senza bloccare altre parti del programma e una chiusura manterrà un valore da riutilizzare nelle successive chiamate di funzione.

Un'altra chiusura; questo cattura un valore ...

let animals = ["fish", "cat", "chicken", "dog"]
let sortedStrings = animals.sorted({ (one: String, two: String) -> Bool in return one > two
}) println(sortedStrings)

4
Sfortunatamente, questo confonde chiusure e lambdas. Le lambda usano spesso le chiusure, perché di solito sono molto più utili se definite a) molto tersamente eb) all'interno e in base a un determinato contesto del metodo, comprese le variabili. Tuttavia, la chiusura effettiva non ha nulla a che fare con l'idea di una lambda, che è fondamentalmente la capacità di definire una funzione di prima classe in atto e passarla in giro per un uso successivo.
Nathan Tuggy,

Mentre voti questa risposta, leggi questo ... Quando dovrei votare? Usa i tuoi voti negativi ogni volta che incontri un post egregiamente sciatto, senza sforzo, o una risposta che è chiaramente e forse pericolosamente errata. La mia risposta potrebbe non avere alcuni dei motivi per utilizzare una chiusura, ma non è né egregiamente sciatta né pericolosamente errata. Ci sono molti documenti e discussioni sulle chiusure incentrate sulla programmazione funzionale e sulla notazione lambda. Tutta la mia risposta dice che questa spiegazione della programmazione funzionale mi ha aiutato a capire le chiusure. Quello e il valore persistente.
Bendrix,

1
La risposta non è forse pericolosa , ma è certamente "chiaramente [...] errata". Usa termini sbagliati per concetti che sono altamente complementari e quindi spesso usati insieme - esattamente dove la chiarezza della distinzione è essenziale. Ciò può causare problemi di progettazione ai programmatori che leggono questo messaggio se non vengono indirizzati. (E poiché, per quanto ne so, l'esempio di codice semplicemente non contiene alcuna chiusura, ma solo una funzione lambda, non aiuta a spiegare perché le chiusure sarebbero utili.)
Nathan Tuggy,

Nathan, questo esempio di codice è una chiusura in Swift. Puoi chiedere a qualcun altro se dubiti di me. Quindi, se si tratta di una chiusura, voteresti?
Bendrix,

1
Apple descrive in modo abbastanza chiaro esattamente cosa significano per chiusura nella loro documentazione . "Le chiusure sono blocchi autonomi di funzionalità che possono essere passati e utilizzati nel codice. Le chiusure in Swift sono simili ai blocchi in C e Objective-C e ai lambda in altri linguaggi di programmazione." Quando si arriva all'implementazione e alla terminologia può essere fonte di confusione .
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.