Perché gli elenchi sono la struttura di dati preferita nei linguaggi funzionali?


45

La maggior parte dei linguaggi funzionali utilizza elenchi collegati come struttura dati immutabile primaria. Perché elenchi e non ad esempio alberi? Gli alberi possono anche riutilizzare percorsi e persino elenchi di modelli.



10
@gnat - questo non è un duplicato di quella domanda. La risposta accettata e corretta a questa domanda è essenzialmente "perché un elenco collegato immutabile consente la condivisione di code tra elenchi aggiornati e elenchi originali", una risposta che viene indicata come parte dello sfondo di questa domanda ...
Jules

9
Un punto di chiarimento è che un "elenco" (il concetto astratto di programmazione) è distinto da un "elenco collegato" (una particolare implementazione di tale concetto), in modo molto simile alla specifica di un linguaggio di programmazione è distinta dalla sua implementazione. La domanda "perché i linguaggi funzionali usano gli elenchi (il concetto di programmazione astratto) come struttura dati principale?" è sottilmente ma fondamentalmente molto diverso dalla domanda "perché le implementazioni comuni dei linguaggi funzionali X, Y e Z usano gli elenchi collegati come struttura di dati principale?"
RM,

I scala Vector (che è implementato come un albero) è leggermente preferito all'Elenco stackoverflow.com/questions/6928327/… In pratica la maggior parte delle persone usa ancora gli Elenchi (da quello che ho visto).
Akavall,

Ho seguito alcuni corsi di programmazione funzionale a Scala e ho usato MOLTO alberi. È solo che l'elenco è più semplice per esempi. Gli alberi vengono utilizzati per problemi di prestazioni. È possibile creare alberi inmutabili riutilizzando parte degli alberi più vecchi proprio mentre si aggiungono elementi a elenchi inmutabili.
Borjab,

Risposte:


56

Perché gli elenchi sono più semplici degli alberi. (Puoi vederlo in modo banale dal fatto che un elenco è un albero degenerato, in cui ogni nodo ha un solo figlio.)

L'elenco di contro è la struttura di dati ricorsiva più semplice possibile di dimensioni arbitrarie.

Guy Steele ha sostenuto durante la progettazione del linguaggio di programmazione della Fortezza che per i calcoli massicciamente paralleli del futuro, sia le nostre strutture di dati che il nostro flusso di controllo dovrebbero essere a forma di albero con più rami, non lineari come sono ora. Ma per il momento, la maggior parte delle librerie della nostra struttura di dati di base sono state progettate con elaborazione sequenziale e iterativa (o ricorsione della coda, non importa, sono la stessa cosa) in mente, non elaborazione parallela.

Si noti che ad esempio in Clojure, le cui strutture di dati sono state progettate specificamente per il mondo parallelo, distribuito e "nuvoloso" di oggi, anche gli array (chiamati vettori in Clojure), probabilmente la struttura di dati più "lineare" di tutti, sono effettivamente implementati come alberi.

Quindi, in breve: un elenco di contro è la struttura di dati ricorsiva persistente più semplice possibile e non è stato necessario scegliere un "default" più complicato. Altri sono ovviamente disponibili come opzioni, ad esempio Haskell ha array, code di priorità, mappe, cumuli, passaggi, tentativi e tutto ciò che si può immaginare, ma il valore predefinito è la semplice lista di contro.


10
Sì, i vettori Clojure sono implementati come alberi, ma è evidente che si tratta di tentativi mappati con array di hash , non di binari standard data Tree a = Leaf a | Branch a (Tree a) (Tree a). Ciò rafforza il tuo argomento "semplicità".
wchargin,

1
I vettori persistenti di FWIW Clojure sono implementati come alberi (come @wchargin sottolinea in qualche modo complessi) per un rapido accesso (logaritmico con una grande base) e aggiornamento di elementi arbitrari, non proprio per operazioni parallele di per sé (la parte immutabile si occupa di questo per alcuni grado). Altri linguaggi FP fanno la stessa scelta (a volte con diversi tipi di alberi) quando vogliono entrambi (ad es. Haskell Sequenceo Scala Vector), ma non usano gli alberi quando hanno bisogno di leggere solo perché possono raggiungerlo in tempo reale costante (es. Haskell's Vectoro F # via .Net's ImmutableArray)
badcook

1
Ad esempio, il pmapping su un vettore in Clojure accede ancora ad ogni elemento in sequenza; la struttura ad albero del vettore è generalmente nascosta all'utente finale.
badcook

5

In realtà, quelle liste sono alberi! Hai nodi con due campi care cdr, che possono contenere più di tali nodi o foglie. L'unica cosa che trasforma quegli alberi in elenchi è la convenzione di interpretare il cdrcollegamento come un collegamento al nodo successivo in un elenco lineare e il carcollegamento come il valore del nodo corrente.

Detto questo, immagino che la prevalenza degli elenchi collegati nella programmazione funzionale sia collegata alla prevalenza della ricorsione sull'iterazione. Quando il tuo unico costrutto di loop a portata di mano è la (ricorsione) di coda, vuoi strutture di dati che siano facili da usare con quello; e le liste collegate sono perfette per questo.


5
dipende dalla lingua. Certo, puoi implementare un albero in Mi piace LISP usando le celle contro, ma in Haskell, ad esempio, avrai bisogno di una struttura completamente separata. E nota anche che la maggior parte dei linguaggi funzionali ha molti più costrutti ciclici rispetto alla ricorsione della coda. Le librerie core di Haskell, ad esempio, forniscono pieghe (sinistra e destra), scansioni, attraversamenti su alberi, iterazioni su chiavi e valori di mappe e molte altre strutture più specialistiche. Certo, sono implementati con ricorsione dietro le quinte, ma l'utente non deve nemmeno pensarci per farlo funzionare.
Jules,

@Jules Tuttavia, la programmazione funzionale è stata sviluppata e fortemente influenzata da linguaggi come LISP. Ovviamente, è possibile rendere più efficiente tutta questa iterazione di elenchi utilizzando array sotto il cofano, oppure aggiungere zucchero sintattico che nasconde la natura di un elenco, ma la pura programmazione funzionale può fare e fa a meno. Inoltre, Haskell è una lingua piuttosto recente (32 anni più giovane di LISP), che aggiunge un po 'di altre idee, zucchero sintattico, ecc. Al puro stile funzionale. Penso che giudicare la programmazione funzionale giudicando Haskell sia un po 'come giudicare i mixer giudicando
thermomix

2
@cmaster mentre Haskell è più giovane ha ancora 27 anni e ha influenzato molte lingue (tra cui Python, Java e C # per citarne alcuni influenti). Giudicare la programmazione funzionale da parte di LISP è come giudicare la programmazione imperativa da parte di ALGOL - entrambi hanno fatto molta strada da allora che diventare irriconoscibili. OK. Lisp è probabilmente ancora rilevante, ma non lo considero "mainstream" e manca molte intuizioni successive, compresi i tipi di ML.
Maciej Piechotka,

1
Non è possibile elaborare la coda di alberi collegati singolarmente in modo ricorsivo o iterativo senza uno stack esplicito se si desidera toccare l'intero albero, quindi non credo che la ricorsione della coda abbia nulla a che fare con esso.
k_g,

@MaciejPiechotka Né Python, Java, né C # possono essere visti come linguaggi funzionali, sono imperativi e aggiungono alcune funzionalità che si ispirano alla programmazione funzionale. Una volta che hai cambiato stato, sei saldamente all'interno del dominio della programmazione imperativa, non funzionale. Sono d'accordo con te, tuttavia, che il LISP non è assolutamente mainstream.
cmaster
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.