In che modo i linguaggi di programmazione puramente funzionali gestiscono i dati in rapida evoluzione?


22

Quali strutture di dati è possibile utilizzare in modo da poter ottenere la rimozione e la sostituzione O (1)? O come puoi evitare situazioni quando hai bisogno di dette strutture?


2
Per quelli di noi che hanno meno familiarità con linguaggi di programmazione puramente funzionali, pensate che potreste fornire un po 'più di background in modo da capire qual è il vostro problema?
FrustratedWithFormsDesigner,

4
@FrustratedWithFormsDesigner I linguaggi di programmazione puramente funzionali richiedono che tutte le variabili siano immutabili, il che a sua volta richiede strutture di dati che creano nuove versioni di se stesse quando "modificate".
Doval,

5
Sei a conoscenza del lavoro di Okasaki su strutture di dati puramente funzionali?

2
Una possibilità è quella di definire una monade per i dati mutabili (vedi ad esempio haskell.org/ghc/docs/4.08/set/sec-marray.html ). In questo modo, i dati mutabili vengono trattati in modo simile a IO.
Giorgio,

1
@CodesInChaos: tuttavia, tali strutture immutabili in genere hanno anche un sovraccarico molto maggiore rispetto ai semplici array. Di conseguenza, non v'è molto una differenza pratica. Ecco perché qualsiasi linguaggio puramente funzionale che punta alla programmazione generale dovrebbe avere un modo di usare strutture mutabili, in qualche modo sicure e compatibili con la semantica pura. La STmonade di Haskell lo fa in modo eccellente.
lasciato circa il

Risposte:


32

Esiste una vasta gamma di strutture dati che sfruttano la pigrizia e altri trucchi per ottenere un tempo costante ammortizzato o persino (per alcuni casi limitati, come le code ) aggiornamenti di tempo costante per molti tipi di problemi. La tesi di dottorato di Chris Okasaki "Strutture di dati puramente funzionali" e il libro con lo stesso nome sono un ottimo esempio (forse il primo importante), ma da allora il campo è avanzato . Queste strutture di dati sono in genere non solo puramente funzionali nell'interfaccia, ma possono anche essere implementate in Haskell puro e linguaggi simili e sono completamente persistenti.

Anche senza nessuno di questi strumenti avanzati, i semplici alberi di ricerca binaria bilanciata forniscono aggiornamenti del tempo logaritmico, quindi la memoria mutabile può essere simulata nel peggiore dei casi con un rallentamento logaritmico.

Esistono altre opzioni, che possono essere considerate imbrogli, ma sono molto efficaci per quanto riguarda lo sforzo di implementazione e le prestazioni del mondo reale. Ad esempio, i tipi lineari o i tipi di unicità consentono l'aggiornamento sul posto come strategia di implementazione per un linguaggio concettualmente puro, impedendo al programma di mantenere il valore precedente (la memoria che sarebbe mutata). Questo è meno generale delle strutture di dati persistenti: ad esempio, non è possibile creare facilmente un registro di annullamento archiviando tutte le versioni precedenti dello stato. È ancora uno strumento potente, sebbene AFAIK non sia ancora disponibile nelle principali lingue funzionali.

Un'altra opzione per introdurre in sicurezza uno stato mutabile in un ambiente funzionale è la STmonade di Haskell. Può essere implementato senza mutazione e bloccando le unsafe*funzioni, si comporta come se fosse solo un elaborato wrapper per il passaggio implicito di una struttura di dati persistente (cfr State.). Ma a causa di un tipo di inganno del sistema che impone l'ordine di valutazione e previene la fuga, può essere tranquillamente implementato con una mutazione sul posto, con tutti i vantaggi in termini di prestazioni.


Potrebbe anche valere la pena menzionare Cerniere che ti danno la possibilità di fare rapidi cambiamenti a fuoco in un elenco o albero
jk.

1
@jk. Sono menzionati nel post di Theoretical Computer Science che ho collegato. Inoltre, sono solo una (beh, una classe) di molte strutture di dati rilevanti e discuterne tutte è fuori portata e di scarsa utilità.

abbastanza giusto, non aveva seguito i collegamenti
jk.

9

Una struttura mutevole economica è lo stack di argomenti.

Dai un'occhiata al tipico calcolo fattoriale in stile SICP:

(defn fac (n accum) 
    (if (= n 1) 
        accum 
        (fac (- n 1) (* accum n)))

(defn factorial (n) (fac n 1))

Come puoi vedere, il secondo argomento facè usato come accumulatore mutabile per contenere il prodotto in rapida evoluzione n * (n-1) * (n-2) * .... Tuttavia, non è in vista alcuna variabile mutabile e non è possibile modificare inavvertitamente l'accumulatore, ad esempio da un altro thread.

Questo è, ovviamente, un esempio limitato.

Puoi ottenere liste immutabili collegate con una sostituzione economica del nodo head (e per estensione qualsiasi parte che inizia dalla testa): fai semplicemente in modo che la nuova testa punti allo stesso nodo successivo della vecchia testa. Funziona bene con molti algoritmi di elaborazione di elenchi ( foldbasati su qualsiasi cosa ).

È possibile ottenere prestazioni piuttosto buone da array associativi basati ad esempio su HAMT . Logicamente ricevi un nuovo array associativo con alcune coppie chiave-valore modificate. L'implementazione può condividere la maggior parte dei dati comuni tra gli oggetti vecchi e quelli appena creati. Questo non è O (1) però; di solito ottieni qualcosa di logaritmico, almeno nel caso peggiore. Gli alberi immutabili, d'altra parte, di solito non subiscono alcuna penalità di prestazione rispetto agli alberi mutabili. Naturalmente, ciò richiede un certo sovraccarico di memoria, di solito tutt'altro che proibitivo.

Un altro approccio si basa sull'idea che se un albero cade in una foresta e nessuno lo sente, non deve produrre suono. Cioè, se puoi dimostrare che un po 'di stato mutato non lascia mai un ambito locale, puoi mutare i dati al suo interno in modo sicuro.

Clojure ha transitori che sono mutevoli "ombre" di strutture di dati immutabili che non perdono al di fuori dell'ambito locale. Clean usa Uniques per ottenere qualcosa di simile (se ricordo bene). Rust aiuta a fare cose simili con puntatori unici controllati staticamente.


1
+1, anche per menzionare tipi unici in Clean.
Giorgio,

@ 9000 Penso di aver sentito che Haskell ha qualcosa di simile ai transitori di Clojure: qualcuno mi corregge se sbaglio.
paul

@paul: Ho una conoscenza molto superficiale di Haskell, quindi se potessi fornire le mie informazioni (almeno una parola chiave a google), includerei felicemente un riferimento alla risposta.
9000

1
@paul Non ne sono così sicuro. Ma Haskell ha un metodo per creare qualcosa di simile a quello di ML refe limitarlo in un certo ambito. Vedi IORefo STRef. E poi naturalmente ci sono TVars e MVars che sono simili, ma con la semantica concomitanti sane (STM per TVars e mutex based per MVars)
Daniel Gratzer

2

Quello che stai chiedendo è un po 'troppo ampio. O (1) rimozione e sostituzione da quale posizione? Il capo di una sequenza? La coda? Una posizione arbitraria? La struttura dei dati da utilizzare dipende da questi dettagli. Detto questo, 2-3 Finger Trees sembrano una delle strutture di dati persistenti più versatili disponibili:

Presentiamo alberi a 2-3 dita, una rappresentazione funzionale di sequenze persistenti che supportano l'accesso alle estremità in tempo costante ammortizzato, e concatenazione e divisione nel tempo logaritmica nella dimensione del pezzo più piccolo.

(...)

Inoltre, definendo l'operazione di divisione in una forma generale, otteniamo una struttura di dati per scopi generali che può fungere da sequenza, coda di priorità, albero di ricerca, coda di ricerca prioritaria e altro.

Le strutture di dati generalmente persistenti hanno prestazioni logaritmiche quando si modificano posizioni arbitrarie. Questo può o non può essere un problema, poiché la costante in un algoritmo O (1) può essere elevata e il rallentamento logaritmico potrebbe essere "assorbito" in un algoritmo globale più lento.

Ancora più importante, le strutture di dati persistenti rendono più facile il ragionamento sul programma e dovrebbe sempre essere la modalità operativa predefinita. Dovresti preferire strutture di dati persistenti quando possibile e utilizzare una struttura di dati mutabili solo dopo aver profilato e determinato che la struttura di dati persistenti è un collo di bottiglia delle prestazioni. Nient'altro è l'ottimizzazione prematura.

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.