Tutte le lingue funzionali usano la garbage collection?

31

Esiste un linguaggio funzionale che consente di usare la semantica dello stack - distruzione deterministica automatica alla fine dello scope?

    
posta user467799 10.03.2012 - 20:38
fonte

3 risposte

10

Non che io sappia, anche se non sono un esperto di programmazione funzionale.

In linea di principio, sembra piuttosto difficile, perché i valori restituiti dalle funzioni possono contenere riferimenti ad altri valori che sono stati creati (nello stack) all'interno della stessa funzione, o potrebbero essere semplicemente passati come parametri, o referenziati da qualcosa passato come parametro In C, questo problema viene affrontato consentendo che i puntatori penzolanti (o, più precisamente, il comportamento non definito) possano verificarsi se il programmatore non risolve le cose. Questo non è il tipo di soluzione che i progettisti di linguaggi funzionali approvano.

Però ci sono potenziali soluzioni. Un'idea è di rendere il lifetime del valore una parte del tipo del valore, insieme ai riferimenti ad esso, e definire regole basate sui tipi che impediscano il ritorno dei valori allocati dallo stack o referenziati da qualcosa restituito, un funzione. Non ho elaborato le implicazioni, ma sospetto che sarebbe orribile.

Per il codice monadico, c'è un'altra soluzione che è (realmente o quasi) monadica, e potrebbe dare un tipo di IORef automaticamente distrutto deterministicamente. Il principio è definire le azioni di "nidificazione". Quando vengono combinati (usando un operatore associativo), questi definiscono un flusso di controllo del nesting - penso "elemento XML", con i valori più a sinistra che forniscono la coppia esterna di tag di inizio e fine. Questi "tag XML" stanno solo definendo l'ordine di azioni monadiche a un altro livello di astrazione.

Ad un certo punto (sul lato destro della catena di composizione associativa) è necessario un qualche tipo di terminatore per terminare il nidificazione - qualcosa per riempire il buco nel mezzo. La necessità di un terminatore è ciò che probabilmente significa che l'operatore di composizione di nidificazione non è monadico, anche se, di nuovo, non sono del tutto sicuro poiché non ho elaborato i dettagli. Poiché tutto quello che applica il terminatore è convertire un'azione di nidificazione in un'azione monadica normale composta, forse no - non necessariamente influenza l'operatore di composizione di nidificazione.

Molte di queste azioni speciali avrebbero un passo null "end-tag" e equipararebbero il passo "begin-tag" con una semplice azione monadica. Ma alcuni rappresenterebbero dichiarazioni variabili. Questi rappresenterebbero il costruttore con il tag begin e il distruttore con il tag end. Quindi ottieni qualcosa come ...

act = terminate ((def-var "hello" ) >>>= \h ->
                 (def-var " world") >>>= \w ->
                 (use-val ((get h) ++ (get w)))
                )

Traduzione in una composizione monadica con il seguente ordine di esecuzione, ogni tag (non elemento) diventa una normale azione monadica ...

<def-var val="hello">  --  construction
  <def-var val=" world>  --  construction
    <use-val ...>
      <terminator/>
    </use-val>  --  do nothing
  </def-val>  --  destruction
</def-val>  --  destruction

Regole come questa potrebbero consentire l'implementazione di RAII in stile C ++. I riferimenti IORef-like non possono sfuggire al loro scopo, per ragioni simili al perché i normali IORef non possono sfuggire alla monade - le regole della composizione associativa non forniscono alcun modo per il riferimento di scappare.

EDIT - Ho quasi dimenticato di dire - c'è un'area precisa di cui non sono sicuro. È importante assicurarsi che una variabile esterna non possa fare riferimento a quella interna, in pratica, quindi devono esserci delle restrizioni su ciò che si può fare con questi riferimenti simili a IORef. Ancora una volta, non ho elaborato tutti i dettagli.

Pertanto, la costruzione potrebbe ad es. aprire un file che si chiude la distruzione. La costruzione potrebbe aprire una presa che si chiude la distruzione. Fondamentalmente, come in C ++, le variabili diventano gestori di risorse. Ma a differenza del C ++, non ci sono oggetti allocati su heap che non possono essere automaticamente distrutti.

Sebbene questa struttura supporti RAII, hai ancora bisogno di un garbage collector. Sebbene un'azione di nidificazione possa allocare e liberare memoria, trattandola come una risorsa, ci sono ancora tutti i riferimenti a valori funzionali (potenzialmente condivisi) all'interno di quel pezzo di memoria e altrove. Dato che la memoria può essere semplicemente allocata nello stack, evitando la necessità di un heap libero, il vero significato (se ce n'è uno) è per altri tipi di gestione delle risorse.

Quindi ciò che si ottiene è separare la gestione delle risorse in stile RAII dalla gestione della memoria, almeno nel caso in cui RAII si basi su un semplice ambito di nidificazione. Hai ancora bisogno di un garbage collector per la gestione della memoria, ma ottieni una pulizia deterministica automatica sicura e tempestiva di altre risorse.

    
risposta data 10.03.2012 - 22:04
fonte
2

Se consideri il C ++ come linguaggio funzionale (ha lambdas), allora è un esempio di un linguaggio che non usa una garbage collection.

    
risposta data 10.03.2012 - 21:30
fonte
2

Devo dire che la domanda è un po 'mal definita perché presuppone che esista una raccolta standard di "linguaggi funzionali". Quasi tutti i linguaggi di programmazione supportano una certa quantità di programmazione funzionale. E quasi ogni linguaggio di programmazione supporta una certa quantità di programmazione imperativa. Dove si disegna la linea per dire quale è un linguaggio funzionale e che è una lingua imperativa, diversa da quella guidata da pregiudizi culturali e dogmi popolari?

Un modo migliore per esprimere la domanda sarebbe: "è possibile supportare la programmazione funzionale in una memoria allocata nello stack". La risposta è, come già detto, molto difficile. Lo stile di programmazione funzionale promuove l'allocazione di strutture dati ricorsive a volontà, che richiede una memoria heap (indipendentemente dal fatto che la raccolta di dati inutili o il conteggio di riferimento). Tuttavia, esiste una tecnica di analisi del compilatore piuttosto sofisticata, chiamata analisi della memoria basata sulla regione , che consente al compilatore di dividere l'heap in grandi blocchi che possono essere allocati e deallocati automaticamente, in modo simile all'allocazione dello stack. La pagina di Wikipedia elenca varie implementazioni della tecnica, sia per linguaggi "funzionali" che "imperativi".

    
risposta data 11.03.2012 - 23:09
fonte