Soluzione alternativa per l'implementazione di operazioni su strutture di dati doppiamente circolari o circolari in lingue con dati immutabili


11

Vorrei imparare come creare grafici ed eseguire alcune operazioni locali su di essi in Haskell, ma la domanda non è specifica per Haskell e al posto dei grafici possiamo prendere in considerazione elenchi doppiamente collegati.

Domanda: quale sarebbe un modo idiomatico o raccomandato per implementare un elenco doppiamente collegato (o altre strutture di dati doppiamente collegate o circolari) e operazioni su di esso in un linguaggio che supporta e sostiene principalmente strutture di dati immutabili (Haskell, Clojure, ecc.) ? In particolare, come utilizzare gli aggiornamenti sul posto, che sono formalmente vietati dalla lingua?

Posso facilmente immaginare che se un'operazione locale viene eseguita su un elenco doppiamente collegato (se viene inserito un elemento, ad esempio), potrebbe non essere necessario copiare l'intero elenco immediatamente a causa della pigrizia della lingua. Tuttavia, poiché l'elenco è doppiamente collegato, se viene modificato in un unico punto, nessuno dei vecchi nodi può essere utilizzato nella nuova versione dell'elenco e dovrebbero essere in qualche modo contrassegnati, copiati, immondizia prima o poi . Ovviamente si tratta di operazioni ridondanti se si utilizza solo la copia aggiornata dell'elenco, ma aggiungerebbe un "overhead" proporzionale alla dimensione dell'elenco.

Ciò significa che per tali compiti i dati immutabili sono semplicemente inappropriati e che i linguaggi dichiarativi funzionali senza supporto "nativo" per i dati mutabili non sono buoni come quelli imperativi? Oppure, c'è qualche soluzione complicata?

PS Ho trovato alcuni articoli e presentazioni su questo argomento su Internet ma ho avuto difficoltà a seguirli, mentre penso che la risposta a questa domanda non dovrebbe richiedere più di un paragrafo e forse un diagramma ... Voglio dire, se c'è nessuna soluzione "funzionale" a questo problema, la risposta probabilmente è "usa C". Se ce n'è uno, allora quanto può essere complicato?


Domande correlate


Citazioni pertinenti

I linguaggi di programmazione puramente funzionali consentono a molti algoritmi di essere espressi in modo molto conciso, ma ci sono alcuni algoritmi in cui lo stato aggiornabile sul posto sembra svolgere un ruolo cruciale. Per questi algoritmi, i linguaggi puramente funzionali, privi di stato aggiornabile, sembrano intrinsecamente inefficienti ( [Ponder, McGeer e Ng, 1988] ).

- John Launchbury e Simon Peyton Jones, Lazy funzionale state thread (1994), anche John Launchbury e Simon Peyton Jones, State in Haskell (1995). Questi articoli introducono il STcostruttore di tipo monadico in Haskell.


4
Consigliato: Okasaki
Robert Harvey,

2
Grazie per il riferimento. Ho trovato la sua tesi .
Alexey,

Questo articolo sembra promettente: algoritmi di ricerca lineare e approfonditi per primi pigri in Haskell (1994), di David King e John Launchbury.
Alexey,

Sembra che un problema simile con gli array sia risolto dal pacchetto diffarray che implementa il DiffArraytipo. Osservando la fonte del pacchetto diffarray , vedo 91 occorrenze di unsafePerformIO. Sembra che la risposta alla mia domanda sia "sì, no, i linguaggi puramente funzionali con dati immutabili non sono appropriati per l'implementazione di algoritmi che normalmente si basano su aggiornamenti sul posto".
Alexey,

La mia soluzione attuale (in Haskell) è quello di utilizzare un dizionario ( Map, IntMap, o HashMap) come deposito e per rendere i nodi contengono gli ID dei nodi collegati. "Tutti i problemi di informatica possono essere risolti da un altro livello di riferimento indiretto."
Alexey,

Risposte:


6

Potrebbero esserci altre strutture di dati immutabili efficienti che si adattano al tuo compito specifico, ma non sono così generali come un elenco doppiamente collegato (che purtroppo è soggetto a bug di modifica simultanea a causa della sua mutabilità). Se si specifica il problema in modo più restrittivo, è possibile trovare una struttura del genere.

La risposta generale per l'attraversamento (relativamente) economico di strutture immutabili sono le lenti. L'idea è che puoi conservare le informazioni sufficienti per ricostruire una struttura immutabile modificata dalle sue parti non modificate e dal pezzo attualmente modificato, e navigare su di essa fino a un nodo vicino.

Un'altra utile struttura è una cerniera . (La parte divertente è che la firma del tipo per una cerniera dell'obiettivo è un derivato matematico scolastico di una firma del tipo della struttura.)

Ecco alcuni link.


1
a seconda di ciò che è necessario potrebbe anche essere utile una cerniera
jk.

Per specificare il mio problema in modo più restrittivo, supponiamo di voler programmare un sistema di riscrittura dei grafici, ad esempio un analizzatore di calcoli lambda basato sulla riscrittura dei grafici.
Alexey,

1
@Alexey: conosci il lavoro delle persone pulite sulla riscrittura dei grafici? wiki.clean.cs.ru.nl/…
Giorgio

1
@Alexey: Non che io sappia: Clean è un cugino di Haskell che è stato sviluppato da solo. Ha anche un meccanismo diverso per gestire gli effetti collaterali (AFAIK si chiama tipi unici). D'altra parte, gli sviluppatori hanno lavorato molto con la riscrittura dei grafici. Quindi potrebbero essere tra le persone migliori che conoscono sia la riscrittura dei grafici sia la programmazione funzionale.
Giorgio,

1
Sono d'accordo sul fatto che una cerniera sembra risolvere il problema con un elenco doppiamente collegato o un albero se voglio navigare e modificare nel punto in cui mi trovo attualmente, ma non è chiaro cosa fare se voglio concentrarmi su più punti contemporaneamente e, ad esempio, scambiare due elementi in due punti distanti. È ancora meno chiaro se può essere usato con strutture "circolari".
Alexey,

2

Haskell non impedisce l'uso di strutture di dati mutabili. Sono fortemente scoraggiati e resi più difficili da usare a causa del fatto che le parti del codice che li usano devono eventualmente restituire un'azione IO (che alla fine deve essere associata all'azione IO che viene restituita dalla funzione principale), ma ciò non rendere impossibile l'uso di tali strutture se ne hai davvero bisogno.

Suggerirei di indagare sull'uso della memoria transazionale del software come via da seguire. Oltre a fornire un modo efficace per implementare strutture mutabili, offre anche garanzie molto utili per la sicurezza delle filettature. Vedi la descrizione del modulo su https://hackage.haskell.org/package/stm e la panoramica della wiki su https://wiki.haskell.org/Software_transactional_memory .


Grazie, proverò a conoscere STM. Sembra che ci sono metodi più a Haskell per avere mutevolezza e lo stato (ho imbatte in MVar, State, ST), ne avrei bisogno di capire le loro differenze e usi previsti.
Alexey,

@Alexey: buon punto in merito ST, IMO dovrebbe essere menzionato nella risposta perché consente di eseguire un calcolo stateful, quindi di eliminare lo stato ed estrarre il risultato come valore puro.
Giorgio,

@Giorgio, è possibile utilizzare Haskell's STcon STM per avere sia la concorrenza che lo stato disponibile?
Alexey,

Solo un altro suggerimento terminologico: l'azione IO principale composta non viene " restituita dalla funzione principale" ma è assegnata alla mainvariabile. :) ( mainnon ha nemmeno una funzione.)
Alexey,

Vedo il tuo punto, ma ancora "variabile" ha una connotazione nella mente della maggior parte delle persone come un valore semplice, piuttosto che un processo che produce un valore, e il principale è chiaramente meglio pensato come il secondo piuttosto che il primo. Il cambiamento che suggerisci, sebbene chiaramente tecnicamente corretto, ha il potenziale per confondere chi non ha familiarità con l'argomento.
Jules il
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.