Problemi nell'implementazione di chiusure in impostazioni non funzionali


18

Nei linguaggi di programmazione, le chiusure sono una caratteristica popolare e spesso desiderata. Wikipedia dice (enfasi sulla mia):

In informatica, una chiusura (...) è una funzione insieme a un ambiente di riferimento per le variabili non locali di quella funzione. Una chiusura consente a una funzione di accedere a variabili al di fuori del suo immediato ambito lessicale.

Quindi una chiusura è essenzialmente un valore di funzione (anonimo?) Che può usare variabili al di fuori del proprio ambito. Nella mia esperienza, ciò significa che può accedere a variabili che rientrano nell'ambito nel suo punto di definizione.

In pratica, il concetto sembra divergere, almeno al di fuori della programmazione funzionale. Lingue diverse implementano semantica diversa, sembra addirittura che ci siano guerre di opinioni. Molti programmatori non sembrano sapere cosa siano le chiusure, considerandole poco più che funzioni anonime.

Inoltre, sembrano esserci grossi ostacoli nell'attuazione delle chiusure. In particolare, Java 7 avrebbe dovuto includerli, ma la funzionalità è stata rimandata a una versione futura.

Perché le chiusure sono così difficili (da capire e) da realizzare? Questa è una domanda troppo ampia e vaga, quindi lasciatemi concentrare di più con queste domande interconnesse:

  • Ci sono problemi nell'esprimere le chiusure nei comuni formalismi semantici (piccolo passo, grande passo, ...)?
  • I sistemi di tipo esistenti non sono adatti per le chiusure e non possono essere estesi facilmente?
  • È problematico allineare le chiusure con una traduzione di procedure tradizionale basata su stack?

Si noti che la domanda riguarda principalmente i linguaggi procedurali, orientati agli oggetti e di scripting in generale. Per quanto ne so, i linguaggi funzionali non hanno alcun problema.


Buona domanda. Le chiusure sono state implementate in Scala e Martin Odersky ha scritto il compilatore Java 1.5, quindi non è chiaro il motivo per cui non sono in Java 7. C # le ha. (Proverò a scrivere una risposta migliore in seguito.)
Dave Clarke,

4
Linguaggi funzionali impuri come Lisp e ML si adattano bene alle chiusure, quindi non ci può essere una ragione semantica intrinseca per essere problematici.
Gilles 'SO- smetti di essere malvagio'

Ho incluso l'oggetto perché ho avuto difficoltà a immaginare come potrebbe apparire un semantico a piccoli passi per le chiusure. Può darsi che le chiusure in sé non siano un problema, ma è difficile includerle in un linguaggio che non è stato progettato pensando a loro.
Raffaello

1
Dai un'occhiata a pdfs.semanticscholar.org/73a2/… - Gli autori di Lua lo hanno fatto in modo molto intelligente e discutono anche dei problemi generali di implementazione delle chiusure
Bulat

Risposte:


10

Posso indirizzarti alla pagina wikipedia del problema Funarg ? Almeno questo è il modo in cui le persone del compilatore facevano riferimento al problema di implementazione della chiusura.

Quindi una chiusura è essenzialmente un valore di funzione (anonimo?) Che può usare variabili al di fuori del proprio ambito. Nella mia esperienza, ciò significa che può accedere a variabili che rientrano nell'ambito nel suo punto di definizione.

Sebbene questa definizione abbia senso, non aiuta a descrivere il problema dell'implementazione di funzioni di prima classe in un linguaggio tradizionale basato su stack di runtime. Quando si tratta di problemi di implementazione, le funzioni di prima classe possono essere approssimativamente divise in due classi:

  • Le variabili locali nelle funzioni non vengono mai utilizzate dopo il ritorno della funzione.
  • Le variabili locali possono essere utilizzate dopo il ritorno della funzione.

Il primo caso (funarg verso il basso) non è così difficile da implementare e può essere trovato anche sui linguaggi procedurali più vecchi, come Algol, C e Pascal. C in qualche modo aggira il problema, poiché non consente le funzioni nidificate, ma Algol e Pascal eseguono la contabilità necessaria per consentire alle funzioni interne di fare riferimento alle variabili di stack della funzione esterna.

Il secondo caso (funargs verso l'alto), d'altra parte, richiede che i record di attivazione siano salvati all'esterno dello stack, nell'heap. Ciò significa che è molto facile perdere le risorse di memoria a meno che il runtime della lingua non includa un garbage collector. Mentre quasi tutto è oggi spazzatura raccolto, richiederne uno è ancora una decisione di progettazione significativa e lo era ancora di più qualche tempo fa.


Per quanto riguarda l'esempio particolare di Java, se ricordo bene, il problema principale non era effettivamente quello di poter implementare chiusure, ma come introdurle al linguaggio in un modo che non era ridondante con funzionalità esistenti (come le classi interne anonime) e che non si scontrano con le funzionalità esistenti (come le eccezioni verificate - un problema che non è banale da risolvere e che la maggior parte delle persone non pensa all'inizio).

Posso anche pensare ad altre cose che rendono le funzioni di prima classe meno banali da implementare, come decidere cosa fare con variabili "magiche" come questa , self o super e come interagire con gli operatori di flusso di controllo esistenti, come break e return (vogliamo consentire resi non locali o no?). Ma alla fine, la recente popolarità delle funzioni di prima classe sembra indicare che le lingue che non le hanno per lo più lo fanno per ragioni storiche o per alcune importanti decisioni di progettazione all'inizio.


1
Conosci qualche lingua che distingue i casi verso l'alto e verso il basso? Nei linguaggi .NET, un metodo generico che prevedeva di ricevere una funzione solo verso il basso poteva ricevere una struttura di tipo generico insieme a un delegato che avrebbe ricevuto una struttura come un byref (in C #, un " refparametro"). Se il chiamante incapsulasse tutte le variabili di interesse nella struttura, il delegato potrebbe essere completamente statico, evitando la necessità di un'allocazione dell'heap. I compilatori non offrono alcun aiuto di sintassi per tali costrutti, ma il Framework potrebbe supportarli.
supercat

2
@supercat: Rust ha diversi tipi di chiusura che ti consentono di applicare in fase di compilazione se una funzione interna dovrà usare l'heap. Tuttavia, ciò non significa che un'implementazione non possa tentare di evitare le allocazioni di heap senza costringerti a preoccuparti di tutti quei tipi extra. Un compilatore può provare a dedurre la durata della funzione oppure può utilizzare i controlli di runtime per salvare pigramente le variabili nell'heap solo quando strettamente necessario ( per maggiori dettagli, consultare la sezione "ambito lessicale" del documento Evolution of Lua )
hugomg

5

Possiamo vedere come vengono implementate le chiusure in C #. La scala delle trasformazioni eseguite dal compilatore C # chiarisce che il loro modo di implementare le chiusure richiede parecchio lavoro. Potrebbero esserci modi più semplici per implementare le chiusure, ma penso che il team di compilatori C # ne sarebbe consapevole.

Considera il seguente pseudo-C # (ho ritagliato un po 'di cose specifiche per C #):

int x = 1;
function f = function() { x++; };
for (int i = 1; i < 10; i++) {
    f();
}
print x; // Should print 9

Il compilatore trasforma questo in qualcosa del genere:

class FunctionStuff {
   int x;
   void theFunction() {
       x++;
   }
}

FunctionStuff theClosureObject = new FunctionStuff();
theClosureObject.x = 1;
for (int i = 1; i < 10; i++) {
    theClosureObject.theFunction();
}
print theClosureObject.x; // Should print 9

(in realtà, la variabile f verrà comunque creata, dove f è un 'delegato' (= puntatore alla funzione), ma questo delegato è ancora associato all'oggetto theClosureObject - ho lasciato questa parte fuori per chiarezza per coloro che non sono familiari con C #)

Questa trasformazione è piuttosto massiccia e complicata: considera le chiusure all'interno delle chiusure e l'interazione delle chiusure con il resto delle funzionalità del linguaggio C #. Posso immaginare che la funzionalità sia stata rinviata per Java, poiché Java 7 ha già molte nuove funzionalità.


Posso vedere dove sta andando; avere più chiusure e l'accesso principale alla stessa variabile sarà disordinato.
Raffaello

Ad essere onesti, questo è più dovuto all'utilizzo del framework OO esistente per l'implementazione delle chiusure che a qualsiasi problema reale con esse. Altre lingue allocano semplicemente le variabili in una struttura separata, senza metodi e quindi lasciano condividere più chiusure, se lo desiderano.
hugomg,

@Raphael: cosa pensi delle chiusure all'interno delle chiusure? Aspetta, lasciami aggiungere.
Alex ten Brink

5

Per rispondere a una parte della tua domanda. Il formalismo descritto da Morrisett e Harper copre la semantica di piccoli e grandi passi di linguaggi polimorfici di ordine superiore contenenti chiusure. Ci sono articoli prima di questi che forniscono i tipi di semantica che stai cercando. Guarda, ad esempio, la macchina SECD . L'aggiunta di riferimenti mutabili o locali mutabili in queste semantiche è semplice. Non vedo che ci siano problemi tecnici nel fornire tale semantica.


Grazie per il riferimento! Non sembra essere una lettura leggera, ma probabilmente ci si può aspettare da un articolo di semantica.
Raffaello

1
@Raphael: Probabilmente ce ne sono di più semplici in giro. Proverò a trovare qualcosa e rispondere a te. In ogni caso, la Figura 8 ha la semantica che stai cercando.
Dave Clarke,

Forse puoi dare una panoramica approssimativa resp. le idee centrali nella tua risposta?
Raffaello

2
@Raphael. Forse potrei rimandarti agli appunti delle lezioni che utilizzo per un corso di linguaggi di programmazione, che ti fornisce una rapida introduzione. Per favore, guarda i volantini 8 e 9.
Uday Reddy,

1
Quel collegamento appare morto o dietro autenticazione invisibile. ( cs.cmu.edu/afs/cs/user/rwh/public/www/home/papers/gcpoly/tr.pdf ). Ho 403 proibito.
Ben Fletcher,
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.