Por que as listas são a estrutura de dados preferida em linguagens funcionais?

45

A maioria das linguagens funcionais usa listas vinculadas como sua principal estrutura de dados imutáveis. Por que listas, e não por exemplo, árvores? As árvores também podem reutilizar caminhos e até listas de modelos.

Filip Haglund
fonte
10
@gnat - esta não é uma duplicata dessa pergunta. A resposta aceita e correta a essa pergunta é, essencialmente, "porque uma lista ligada imutável permite compartilhar rabo entre listas atualizadas e originais", uma resposta que é apontado como parte do plano de fundo para esta pergunta ...
Jules
9
Um ponto de esclarecimento é que uma "lista" (o conceito abstrato de programação) é distinta de uma "lista vinculada" (uma implementação específica desse conceito), bem como a especificação de uma linguagem de programação é diferente de sua implementação. A pergunta "por que linguagens funcionais usam listas (o conceito abstrato de programação) como sua principal estrutura de dados?" é sutil mas fundamentalmente muito diferente da pergunta "por que as implementações comuns das linguagens funcionais X, Y e Z usam listas vinculadas como sua principal estrutura de dados?"
RM
Eu scala Vector (que é implementado como uma árvore) é ligeiramente preferido para Listar stackoverflow.com/questions/6928327/… Na prática, a maioria das pessoas ainda usa Listas (pelo que vi).
Akavall 4/09/17
Fiz alguns cursos de Programação Funcional em Scala e usei MUITO com árvores. É apenas que a lista é mais simples para exemplos. Árvores são usadas para problemas de desempenho. Você pode criar árvores imutáveis ​​reutilizando parte de árvores antigas, assim como adicionar elementos a listas imutáveis.
Borjab 4/17/17

Respostas:

56

Porque listas são mais simples que árvores. (Você pode ver isso trivialmente pelo fato de uma lista ser uma árvore degenerada, onde cada nó tem apenas um único filho.)

A lista de contras é a estrutura de dados recursiva mais simples possível, de tamanho arbitrário.

Guy Steele argumentou durante o projeto da linguagem de programação Fortress que, para os cálculos massivamente paralelos do futuro, nossas estruturas de dados e nosso fluxo de controle devem ter a forma de uma árvore com vários ramos, não lineares como são agora. Mas, por enquanto, a maioria das nossas bibliotecas principais de estrutura de dados foi projetada com processamento sequencial e iterativo (ou recursão de cauda, ​​isso realmente não importa, são a mesma coisa) em mente, não com processamento paralelo.

Observe que, por exemplo, em Clojure, cujas estruturas de dados foram projetadas especificamente para o mundo paralelo, distribuído e "nublado" de hoje, mesmo matrizes (chamadas vetores em Clojure), provavelmente a estrutura de dados mais "linear" de todas, são realmente implementadas como árvores

Portanto, resumindo: uma lista de contras é a estrutura de dados recursiva persistente mais simples possível e não foi necessário escolher um "padrão" mais complicado. Outros estão obviamente disponíveis como opções, por exemplo, Haskell possui matrizes, filas prioritárias, mapas, pilhas, treaps, tentativas e tudo o que você possa imaginar, mas o padrão é a lista de contras simples.

Jörg W Mittag
fonte
10
Sim, os vetores Clojure são implementados como árvores - mas é importante notar que são tentativas mapeadas de matriz de hash , e não os seus binários padrão data Tree a = Leaf a | Branch a (Tree a) (Tree a). Isso reforça seu argumento de "simplicidade".
wchargin
1
Os vetores persistentes do FWIW Clojure são implementados como (como @wchargin aponta um pouco complexo) árvores para acesso rápido (logarítmico com uma grande base) e atualização de elementos arbitrários, não para operações paralelas em si (a parte imutável cuida disso para alguns grau). Outras linguagens FP fazem a mesma escolha (às vezes com diferentes tipos de árvores) quando desejam as duas (por exemplo, de Haskell Sequenceou Scala Vector), mas não as usam quando precisam apenas de ler, pois podem conseguir isso em tempo constante (por exemplo, de Haskell Vectorou F # através da Net ImmutableArray)
badcook
1
Por exemplo, o pmapping sobre um vetor no Clojure ainda acessa cada elemento sequencialmente; a estrutura em árvore do vetor geralmente está oculta do usuário final.
badcook
5

Na verdade, essas listas são árvores! Você tem nós com dois campos care cdr, que podem conter mais desses nós ou folhas. A única coisa que transforma essas árvores em listas é a convenção de interpretar o cdrlink como um link para o próximo nó em uma lista linear e o carlink como o valor do nó atual.

Dito isso, acho que a prevalência de listas vinculadas na programação funcional está ligada à prevalência de recursão sobre a iteração. Quando sua única construção em loop disponível é a recursão (final), você deseja estruturas de dados que sejam fáceis de usar com isso; e listas vinculadas são perfeitas para isso.

cmaster
fonte
5
isso depende do idioma. Claro, você pode implementar uma árvore em curtidas de LISP usando células contras, mas em Haskell, por exemplo, você precisará de uma estrutura totalmente separada. E observe também que a maioria das linguagens funcionais possui muito mais construções de loop do que a recursão da cauda. As bibliotecas principais da Haskell, por exemplo, fornecem dobras (esquerda e direita), varreduras, travessias sobre árvores, iterações sobre chaves e valores de mapas e muitas outras estruturas mais especializadas. Claro, eles são implementados com recursão nos bastidores, mas o usuário nem precisa pensar nisso para fazê-los funcionar.
Jules
@Jules No entanto, a programação funcional foi desenvolvida e altamente influenciada por linguagens como o LISP. Obviamente, é possível tornar toda essa iteração de lista mais eficiente usando matrizes sob o capô, ou adicionar açúcar sintático que oculte a natureza de uma lista, mas a programação funcional pura pode fazer e deixa de funcionar. Além disso, Haskell é uma linguagem bastante recente (32 anos mais nova que o LISP), que adiciona várias outras idéias, açúcar sintático etc. ao puro estilo funcional. Eu acho que, a julgar programação funcional por julgar Haskell é um pouco como a julgar misturadores por julgar thermomix ...
cmaster
2
O @cmaster, enquanto Haskell é mais jovem, ainda tem 27 anos e influenciou muitas linguagens (incluindo Python, Java e C #, para citar apenas alguns influentes). Julgar a programação funcional pelo LISP é como julgar a programação imperativa da ALGOL - ambos percorreram um longo caminho desde que se tornaram irreconhecíveis. ESTÁ BEM. Provavelmente o Lisp ainda é relevante, mas eu não o consideraria "mainstream" e perdi muitas informações posteriores, incluindo os tipos de ML.
Maciej Piechotka
1
Você não pode processar árvores ligadas unicamente de forma recursiva ou iterativa sem uma pilha explícita se quiser tocar em toda a árvore; portanto, não acho que a recursão da cauda tenha algo a ver com isso.
k_g
@MaciejPiechotka Nem Python, Java nem C # podem ser vistos como linguagens funcionais, são imperativos e adicionam alguns recursos inspirados na programação funcional. Depois de mudar de estado, você estará firmemente dentro do domínio da programação imperativa, não funcional. Eu concordo com você, no entanto, que o LISP definitivamente não é popular.
C5