Domanda

Ho scoperto che molte persone usano le parole chiusura e blocco in modo intercambiabile. Molte di queste persone non riescono a spiegare di cosa stanno parlando.

Alcuni programmatori Java (anche quelli di consulenze molto costose) parlano di classi interne anonime come "blocchi". e "chiusure" - ma so che non è vero. (Non è possibile passare variabili mutabili dall'ambito del metodo in cui sono definiti ...)

Sto cercando:

  • una precisa definizione informatica di un blocco
  • una precisa definizione informatica di chiusura
  • e chiarimento sulla differenza tra i due.

Mi piacerebbe davvero vedere link, articoli o prenotare riferimenti su questi per favore .

È stato utile?

Soluzione

Mentre un blocco è solo un pezzo di codice che può essere composto da dichiarazioni e dichiarazioni ma nient'altro, una chiusura è un vero oggetto di prima classe, un vero variabile che ha un blocco come valore.

La differenza principale è che un blocco raggruppa semplicemente le istruzioni (ad esempio il corpo di un'istruzione while ), mentre una chiusura è una variabile che contiene del codice che può essere eseguito.

Se di solito hai una chiusura, puoi passarla come parametro alle funzioni, curvarla e decurificarla e chiamarla principalmente!

Closure c = { println 'Hello!' }
/* now you have an object that contains code */
c.call()

Naturalmente le chiusure sono più potenti, sono variabili e possono essere utilizzate per definire il comportamento personalizzato degli oggetti (mentre di solito dovevi usare interfacce o altri approcci OOP nella programmazione).

Puoi pensare a una chiusura come a una funzione che contiene ciò che quella funzione fa dentro di sé.

I blocchi sono utili perché consentono l'ambito delle variabili. Di solito, quando si definisce una variabile all'interno di un ambito, è possibile sovrascrivere le definizioni esterne senza problemi e nuove definizioni saranno presenti solo durante l'esecuzione del blocco.

for (int i = 0; i < 10; ++i)
{
     int t = i*2;
     printf("%d\r\n", t);
}

t è definito all'interno del blocco (il corpo dell'istruzione for ) e durerà solo all'interno di quel blocco.

Altri suggerimenti

Un blocco è qualcosa di sintattico - Un'unità logica di istruzioni (più correlata a scope che a chiusura ).

if (Condition) {
    // Block here 
} 
else {
    // Another block
}

Una chiusura è correlata a funzioni o classi anaudiche - Un oggetto (funzione) anonimo, un pezzo di codice che è legato a un ambiente (con le sue variabili).

def foo() {
   var x = 0
   return () => { x += 1; return x }
}

Qui foo restituisce una chiusura! La variabile locale x persiste anche dopo la chiusura di foo e può essere incrementata attraverso le chiamate della funzione anonima restituita

val counter = foo()
print counter() // Returns 2
print counter() // Return 3

Nota che è solo Ruby in cui blocco e chiusura sono trattati in modo simile poiché ciò che Ruby chiama blocco è una chiusura:

(1..10).each do |x|
    p x
end

ogni -method viene passato una funzione di chiusura (prendendo un parametro x) che viene chiamato blocco in Ruby.

Qui c'è molta confusione perché ci sono termini con più definizioni e più cose distinte che si confondono semplicemente perché di solito si trovano insieme.

Innanzitutto, abbiamo "blocco". Questo è solo un pezzo di codice lessicale che crea un'unità - il corpo di un ciclo, per esempio. Se la lingua ha effettivamente un ambito di blocco, è possibile definire le variabili che esistono solo all'interno di quel blocco di codice.

In secondo luogo, abbiamo un codice richiamabile come tipo di valore. Nei linguaggi funzionali, questi sono valori di funzione - a volte chiamati "funs", "funzioni anonime" (poiché la funzione si trova nel valore, non nel nome a cui è assegnata; non è necessario un nome per chiamarli) o "quots lambdas" (dall'operatore utilizzato per crearli nel Calcolo Lambda di Church). Possono essere chiamati "chiusure", ma non sono automaticamente chiusure vere; per qualificarsi, devono incapsulare ("chiudere oltre") l'ambito lessicale che circonda la loro creazione - vale a dire, le variabili definite al di fuori dell'ambito della funzione stessa ma nell'ambito della sua definizione sono ancora disponibili ogni volta che viene chiamata la funzione, anche se il punto di chiamata è dopo che la variabile referenziata sarebbe altrimenti uscita dal campo di applicazione e avrebbe riciclato la sua memoria.

Ad esempio, considera questo Javascript:

function makeClosure() {
  var x = "Remember me!";
  return function() {
    return "x='" + x + "'";
  }
}

// console.log(x); 
// The above is an error; x is undefined
var f = makeClosure();
console.log(f());
// The above outputs a string that includes x as it existed when f was created.

La variabile x è definita solo all'interno del corpo della funzione makeClosure ; al di fuori di tale definizione, non esiste. Dopo aver chiamato makeClosure , il x dichiarato al suo interno dovrebbe sparire. Ed è, dal punto di vista della maggior parte del codice. Ma la funzione restituita da makeClosure è stata dichiarata mentre esisteva x , quindi ha ancora accesso ad essa quando la chiami in seguito. Questo lo rende una vera chiusura.

Puoi avere valori di funzione che non sono chiusure, perché non mantengono l'ambito. Puoi anche avere chiusure parziali; I valori delle funzioni di PHP conservano solo variabili specifiche che devono essere elencate al momento della creazione del valore.

Puoi anche avere valori di codice richiamabili che non rappresentano affatto intere funzioni. Smalltalk chiama queste "chiusure a blocchi", mentre Ruby le chiama "procs", anche se molti rubini le chiamano semplicemente "blocchi", perché sono la versione reificata di ciò che è stato creato da {. .. Sintassi } o do ... end . Ciò che li distingue da lambda (o "chiusure di funzioni") è che non introducono un nuovo livello di chiamata. Se il codice nel corpo di una chiusura di blocco richiama return , ritorna dalla funzione / metodo esterno in cui esiste la chiusura del blocco, non solo dal blocco stesso.

Questo comportamento è fondamentale per preservare ciò che R.D. Tennent ha etichettato il "principio di corrispondenza", che afferma che dovresti essere in grado di sostituire qualsiasi codice con una funzione inline contenente quel codice nel corpo e chiamato immediatamente. Ad esempio, in Javascript, puoi sostituirlo:

x=2
console.log(x)

con questo:

(function(){x = 2;})();
console.log(x)

Questo esempio non è molto interessante, ma la capacità di fare questo tipo di trasformazione senza influenzare il comportamento del programma gioca un ruolo chiave nel refactoring funzionale. Ma con lambdas, non appena hai incorporato le istruzioni return , il principio non vale più:

function foo1() {
  if (1) {
    return;
  }
  console.log("foo1: This should never run.")
}
foo1()
function foo2() {
  if (1) {
    (function() { return; })();
  }
  console.log("foo2: This should never run.")
}
foo2()

La seconda funzione è diversa dalla prima; console.log viene eseguito perché return ritorna solo dalla funzione anonima, non da foo2 . Questo infrange il principio di corrispondenza.

Questo è il motivo per cui Ruby ha sia procs che lambdas, anche se la distinzione è una fonte perenne di confusione per i neofiti. Sia procs che lambdas sono oggetti della classe Proc , ma si comportano diversamente, come indicato sopra: un return ritorna dal corpo di un lambda, ma ritorna dal metodo che circonda un proc.

def test
  p = proc do return 1 end
  l = lambda do return 1 end
  r = l[]
  puts "Called l, got #{r}, still here."
  r = p[]
  puts "Called p, got #{r}, still here?"
end

Il precedente metodo test non arriverà mai al secondo put , poiché l'invocazione di p causerà test per restituire immediatamente (con un valore di ritorno di 1). Se Javascript avesse delle chiusure a blocchi, potresti fare la stessa cosa, ma non è così (anche se c'è una proposta per aggiungerle).

Quello forte e barbuto ha questo da dire su Closures and Blocks:

http://martinfowler.com/bliki/Closure.html

Ad un certo punto afferma che una chiusura è un blocco che può essere passato come argomento a un metodo.

I termini che stai usando sono comunemente usati insieme in questi giorni in Ruby, sebbene i costrutti apparissero in precedenza in Algol, Smalltalk e Scheme. Vorrei citare lo standard Ruby, se ce n'era uno.

Non sono sicuro di poter rispondere alla tua domanda esatta, ma posso illustrare. Mi scuso se lo sai già ...

def f &x
  yield
  x
end

def g
  y = "block"
  t = f { p "I'm a #{y}" }
  y = "closure"
  t
end

t = g
t.call

E ...

$ ruby exam.rb
"I'm a block"
"I'm a closure"
$ 

Quindi un blocco è una sequenza anonima simile a una funzione di codice associata a una chiamata di metodo. È utilizzato in tutte le API di Ruby. Quando rendi abbastanza facile creare una funzione anonima, risultano utili per ogni genere di cose.

Ma nota che dopo che f ritorna, quindi g ritorna, abbiamo mantenuto il blocco restituendolo da f (come x ) e quindi da g (come t ). Ora chiamiamo il blocco una seconda volta. Ancora una volta, tieni presente che g () è tornato. Ma il blocco si riferisce a una variabile locale in un'istanza di funzione (e ambito) che non esiste più ?! E ottiene il nuovo valore di y ?!

Quindi una chiusura è un oggetto simile a una funzione che è chiuso sul suo ambito lessicale. Sono piuttosto difficili da implementare perché distruggono il modello fai-da-a-pila che è così utile per le variabili locali nelle istanze di chiamata di funzione.


1. Il rubino ha vari gusti di oggetti funzione simili alla chiusura; Questo è solo uno di loro.

  

5

Questo è un intero .

  

Int workDaysInAWeek = 5

Questa è una variabile intera e può essere impostata su un intero diverso. (Se le circostanze ti impediscono di modificare quel valore, può essere chiamato una costante .)

Considerando che i numeri precedenti riguardano, blocchi e chiusure riguardano algoritmi. La distinzione tra blocchi e chiusure , rispettivamente, equivale anche a quanto sopra.

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