A resposta da @ sacundim é quase sempre verdadeira, mas também existem outras informações importantes sobre o trade-off sobre designs de idiomas e requisitos práticos.
Objetos e referências
Essas linguagens geralmente exigem (ou assumem) objetos com extensões dinâmicas não acopladas (ou na linguagem de C, durante a vida útil , embora não sejam exatamente as mesmas devido às diferenças de significado dos objetos entre essas linguagens, veja abaixo) por padrão, evitando referências de primeira classe ( por exemplo, ponteiros de objeto em C) e comportamento imprevisível nas regras semânticas (por exemplo, comportamento indefinido da ISO C relacionado à semântica).
Além disso, a noção de objetos (de primeira classe) nessas linguagens é conservadora e restritiva: nada de propriedades "locativas" são especificadas e garantidas por padrão. Isso é completamente diferente em algumas linguagens semelhantes ao ALGOL, cujos objetos não têm extensões dinâmicas não acopladas (por exemplo, em C e C ++), onde objetos basicamente significam alguns tipos de "armazenamento digitado", geralmente acoplados a locais de memória.
Codificar o armazenamento nos objetos traz alguns benefícios adicionais, como poder anexar efeitos computacionais determinísticos ao longo da vida útil, mas esse é outro tópico.
Problemas de simulação de estruturas de dados
Sem referências de primeira classe, as listas vinculadas individualmente não podem simular muitas estruturas de dados tradicionais (ansiosas / mutáveis) de maneira eficaz e portável, devido à natureza da representação dessas estruturas de dados e às operações primitivas limitadas nesses idiomas. (Pelo contrário, em C, você pode derivar listas vinculadas com bastante facilidade, mesmo em um programa estritamente conforme .) E essas estruturas de dados alternativas, como matrizes / vetores, têm algumas propriedades superiores em comparação às listas vinculadas individualmente na prática. É por isso que o R 5 RS introduz novas operações primitivas.
Mas existem tipos de vetor / matriz de diferenças versus listas duplamente vinculadas. Uma matriz geralmente é assumida com complexidade de tempo de acesso O (1) e menos sobrecarga de espaço, que são excelentes propriedades não compartilhadas por listas. (Embora estritamente falando, nenhum dos dois é garantido pela ISO C, mas os usuários quase sempre esperam isso e nenhuma implementação prática violaria essas garantias implícitas com muita clareza.) OTOH, uma lista duplamente vinculada geralmente torna ambas as propriedades ainda piores do que uma lista isolada , enquanto a iteração para trás / para frente também é suportada por uma matriz ou um vetor (junto com índices inteiros) com ainda menos sobrecarga. Portanto, uma lista duplamente vinculada não apresenta um desempenho melhor em geral. Pior ainda, o desempenho sobre a eficiência do cache e a latência na alocação dinâmica de memória das listas são catastroficamente piores que o desempenho para matrizes / vetores ao usar o alocador padrão fornecido pelo ambiente de implementação subjacente (por exemplo, libc). Portanto, sem um tempo de execução muito específico e "inteligente" otimizando bastante essas criações de objetos, os tipos de matriz / vetor geralmente são preferidos às listas vinculadas. (Por exemplo, usando ISO C ++, há uma ressalva de questd::vector
deve ser preferido std::list
por defeito.) Assim, para introduzir novas primitivas de suporte especificamente listas encadeadas (doubly-) não é definitivamente tão benéfico como a matriz de suporte / estruturas de dados do vetor em prática.
Para ser justo, as listas ainda têm algumas propriedades específicas melhores que matrizes / vetores:
- As listas são baseadas em nós. A remoção de elementos das listas não invalida a referência a outros elementos em outros nós. (Isso também se aplica a algumas estruturas de dados em árvore ou gráfico.) OTOH, matrizes / vetores podem fazer com que a posição de rastreamento seja invalidada (com realocação maciça em alguns casos).
- As listas podem emendar no tempo O (1). A reconstrução de novas matrizes / vetores com as atuais é muito mais cara.
No entanto, essas propriedades não são muito importantes para um idioma com suporte para listas vinculadas individualmente integradas, que já é capaz de tal uso. Embora ainda existam diferenças, em idiomas com extensões dinâmicas obrigatórias de objetos (o que geralmente significa que existe um coletor de lixo mantendo as referências pendentes), a invalidação também pode ser menos importante, dependendo das intenções. Portanto, os únicos casos em que as listas duplamente vinculadas vencem podem ser:
- São necessários requisitos de garantia de não realocação e de iteração bidirecional. (Se o desempenho do acesso ao elemento for importante e o conjunto de dados for grande o suficiente, eu escolheria árvores de pesquisa binária ou tabelas de hash.
- São necessárias operações de emenda bidirecional eficientes. Isso é consideravelmente raro. (Apenas atendo aos requisitos apenas na implementação de algo como registros lineares de histórico em um navegador.)
Imutabilidade e alias
Em uma linguagem pura como Haskell, os objetos são imutáveis. O objeto do esquema é frequentemente usado sem mutação. Tal fato torna possível melhorar efetivamente a eficiência da memória com a internação de objetos - compartilhamento implícito de vários objetos com o mesmo valor em tempo real.
Essa é uma estratégia agressiva de otimização de alto nível no design da linguagem. No entanto, isso envolve problemas de implementação. Na verdade, ele introduz aliases implícitos nas células de armazenamento subjacentes. Isso torna a análise de aliasing mais difícil. Como resultado, é possível que haja menos possibilidades de eliminar a sobrecarga de referências de primeira classe, mesmo os usuários nunca as tocam. Em idiomas como Scheme, uma vez que a mutação não está totalmente descartada, isso também interfere no paralelismo. No entanto, pode ser bom em um idioma lento (que já apresenta problemas de desempenho causados por thunks).
Para programação de propósito geral, essa escolha do design da linguagem pode ser problemática. Mas com alguns padrões de codificação funcional comuns, as linguagens ainda parecem funcionar bem.