Ogni funzione è una chiusura?

5

Wikipedia dice che quella chiusura - è una funzione, che ha accesso a variabili, dichiarate al di fuori della funzione. C'è anche un esempio:

function startAt(x)
   function incrementBy(y)
       return x + y
   return incrementBy

variable closure1 = startAt(1)
variable closure2 = startAt(5)

Ma secondo la maggior parte dei linguaggi di programmazione (inclusi python, javascript, swift, ecc.) il prossimo esempio è corretto (scritto in python):

# Script starts
test = "check"

def func(test2):

    def func2():
        return test2 == test

    return func2()

print func("check") // returns TRUE
# Script ends

Fondamentalmente, func non è una chiusura, ma ovviamente utilizza% variabiletest, dichiarata al di fuori della funzione. Significa che func IS è una chiusura?

Anche in C ++ puoi eseguire questo:

std::string test = "check";

bool func(std::string test2) {
    if (test2 == test)
        return true;
    else
        return false;
}

int main() {
    if (func("check"))
        std::cout << "TRUE" << std::endl; // prints TRUE
}

Alla fine, questo rende ogni funzione una chiusura. Dove mi sbaglio?

    
posta Hast 17.11.2014 - 20:59
fonte

2 risposte

25

No, non tutte le funzioni sono chiuse.

Wikipedia dice:

... closure ... is a function or reference to a function together with a referencing environment — a table storing a reference to each of the non-local variables (also called free variables or upvalues) of that function.

Aggiungerei "non locale e non globale", ma l'idea è corretta.

Né i tuoi esempi C ++ né quelli Python usano chiusure. In entrambi i casi, le sole regole di scoping consentono alle funzioni di vedere il loro ambito esterno e l'ambito globale.

La "chiusura" si verifica nel 1o esempio: incrementBy viene costruita e quindi restituita dalla sua funzione esterna, cattura argomento x . Quando assegni variable closure1 = startAt(1) , finisci per avere una chiusura (funzione) all'interno di closure1 var quale argomento catturato, quale valore è successo a 1 , quindi quando chiami closure1(2) il risultato è 3 ( 1 + 2 ).

Pensa a come memorizzare alcune informazioni sull'ambito della dichiarazione di closure: incrementBy conserva una memoria circa gli interni di startAt , in particolare un valore del suo argomento x .

Nel calcolo lambda, come so, quelle variabili "non locali" sono chiamate "libere" e le funzioni con variabili libere sono chiamate "termini aperti". La chiusura è un processo di "chiusura" dei termini aperti "fissando" i valori di quelle variabili libere nella "tabella ambientale" sopra menzionata. Da qui il nome.

Vale la pena notare che in Python e JS la chiusura avviene implicitamente, mentre in PHP devi dire esplicitamente quali variabili vuoi chiudere (capture): link - nota use parola chiave nelle dichiarazioni:

// equivalent to the 1st example
function startAt($x) { //        vvvvvvvv          vv
    $incrementBy = function ($y) use ($x) { return $x + $y };
    return $incrementBy;
}
    
risposta data 17.11.2014 - 21:50
fonte
4

Le chiusure sono un modo efficace per implementare funzioni.

Affermo che ogni funzione è concettualmente una chiusura, anche nelle poche lingue che non le possiedono.

Le variabili chiuse sono quindi costanti o dati statici all'interno del codice. Ma in chiusure a tutti gli effetti (come in Ocaml, Scheme, Common Lisp o C ++ 11) le variabili chiuse e il codice sono nella chiusura stessa.

Ad esempio, in C (e in C ++ 98) le funzioni sono chiusure, ma le loro variabili chiuse (o gratuite ) sono limitate a static o variabili globali.

Chiusure offre anche la possibilità di funzioni anonime, ad es. lambda, che creano chiusure con nuove variabili chiuse (e questo è qualcosa che non è possibile nello standard C). Questa è l'operazione astrazione di λ-calculus (che non esiste in C, questo è il motivo per cui ogni funzione di callback -eg in GTK- richiede alcuni "dati client" come parametro aggiuntivo).

Da un punto di vista dell'implementazione, una funzione è sempre un codice con alcuni dati richiesti dal codice. I dati sono le variabili chiuse. Per il codice C compilato, i dati sono cablati nel codice come variabili fisse globali o statiche (e la modifica di queste variabili richiede la ricompilazione) o come costanti letterali. Quindi l'unico modo di fare un'astrazione sarebbe di rigenerare qualche nuovo codice con alcune nuove variabili in esso.

L'astrazione non esiste nello standard C perché non c'è modo di generare nuove funzioni portabili. Ma potresti usare strani trucchi specifici per l'implementazione. Immagina di voler implementare la funzione del generatore di traduzioni. In ocaml, la funzione per generare la funzione translate-by delta è

 let transl delta = fun x -> x + delta

e questo sta generando una nuova chiusura per ogni invocazione. Quindi in

 let t3 = transl 3 in t3 5

viene generata una nuova chiusura t3 (ma forse l'ottimizzatore la sta rimuovendo) e il risultato è 8 (che è 3 + 5).

Potresti fare trucchi fasulli in C: su Linux, per esempio generare al runtime un nuovo file testuale t3mod.c contenente

 int t3(int y) { return y+3; };

e dovresti compilare quel file t3mod.c in un oggetto condiviso t3mod.so e dlopen esso poi dlsym usando "t3" . Questo modo forzato (usando la generazione del codice C in runtime, quindi caricandolo dinamicamente) è un modo di implementare le astrazioni in C ed è un modo per generare dinamicamente nuovi puntatori di funzione. Allo stesso modo, potresti utilizzare le librerie JIT che compilano come ad es. libjit , LLVM (o < a href="https://gcc.gnu.org/wiki/JIT"> libgccjit in futuro GCC 5). Leggi anche la valutazione parziale & eval & programmazione multistadio (ad es. Meta OCaml ...). Vedi anche le voci del blog di J.Pitrat su meta -commutazione combinata & meta-bug .

    
risposta data 18.11.2014 - 06:53
fonte

Leggi altre domande sui tag