Qual é a vantagem das listas de palavras-chave?

101

No elixir, temos Mapas:

> map = %{:a => "one", :b => "two"} # = %{a: "one", b: "two"}
> map.a                             # = "one"
> map[:a]                           # = "one"

Também temos listas de palavras-chave:

> kl = [a: "one", b: "two"]       # = [a: "one", b: "two"]
> kl2 = [{:a, "one"},{:b, "two"}] # = [a: "one", b: "two"]
> kl == kl2                       # = true
> kl[:a]                          # = "one"
> kl.a                            # = ** (ArgumentError)

Por que ambos?

Sintaxe? É porque as Listas de Palavras-chave têm uma sintaxe mais flexível, permitindo que sejam definidas sem curvas e até mesmo sem colchetes como o último parâmetro de uma chamada de função? Então por que não dar ao Maps esse açúcar sintático?

Chaves duplicadas? É porque as listas de palavras-chave podem ter chaves duplicadas? Por que você deseja acesso ao estilo de mapa e chaves duplicadas?

Atuação? É porque as listas de palavras-chave têm melhor desempenho? Então por que o Maps? E os mapas não deveriam ter mais desempenho ao procurar membros por chave do que uma lista de tuplas?

JS Array e Ruby Hash gostam de aparência? É isso?

Eu entendo que estruturalmente eles são representações de dados diferentes. Para mim, parece que as listas de palavras-chave no elixir servem para complicar a linguagem por meio de sintaxe excepcional (3 variantes sintáticas diferentes), sobreposição de casos de uso com mapas e um benefício pouco claro.

Qual é a vantagem de usar listas de palavras-chave?

Greggreg
fonte

Respostas:

143
                   ┌──────────────┬────────────┬───────────────────────┐
                   │ Keyword List │ Map/Struct │ HashDict (deprecated) │
┌──────────────────┼──────────────┼────────────┼───────────────────────┤
│ Duplicate keys   │ yes          │ no         │ no                    │
│ Ordered          │ yes          │ no         │ no                    │
│ Pattern matching │ yes          │ yes        │ no                    │
│ Performance¹     │ —            │ —          │ —                     │
│ ├ Insert         │ very fast²   │ fast³      │ fast⁴                 │
│ └ Access         │ slow⁵        │ fast³      │ fast⁴                 │
└──────────────────┴──────────────┴────────────┴───────────────────────┘

As listas de palavras-chave são leves e têm uma estrutura simples por baixo, o que as torna muito flexíveis. Você pode pensar neles como sintaxe de açúcar no topo de uma convenção Erlang, facilitando a interface com Erlang sem escrever um código muito feio. Por exemplo, listas de palavras-chave são usadas para representar argumentos de função, que é uma propriedade herdada de Erlang. Em alguns casos, as listas de palavras-chave são sua única opção, especialmente se você precisar de chaves ou pedidos duplicados. Eles simplesmente têm propriedades diferentes das outras alternativas, o que os torna mais adequados para algumas situações e menos para outras.

Mapas (e Structs) são usados ​​para armazenar dados reais de carga útil, desde que tenham uma implementação baseada em hash. As listas de palavras-chave internamente são apenas listas que precisam ser percorridas para cada operação, portanto, não têm as propriedades de estruturas de dados de valor-chave clássicas, como acesso de tempo constante.

O Elixir também foi apresentado HashDictcomo uma solução alternativa para o baixo desempenho dos mapas na época em que foi escrito . No entanto, isso foi corrigido agora a partir do Elixir 1.0.5 / Erlang 18.0 e HashDict será descontinuado em versões futuras .

Se você se aprofundar na biblioteca padrão Erlang, verá ainda mais estruturas de dados que armazenam pares de chave / valor:

  • proplists - semelhantes às listas de palavras-chave Elixir
  • maps - igual aos mapas Elixir
  • dict - dicionários de valor-chave construídos a partir de primitivos Erlang
  • gb_trees - árvore balanceada geral

Você também tem essas opções quando precisa armazenar pares de chave / valor em vários processos e / ou VMs:

  • ets / dets - (baseado em disco) armazenamento de termos Erlang
  • mnesia - banco de dados distribuído

¹ De um modo geral, mas é claro que depende ™.

² O melhor caso é apenas adicionar uma lista.

³ Aplica-se ao Elixir 1.0.5 e superior, pode ser mais lento em versões anteriores.

HashDictestá sendo descontinuado.

⁵ Requer uma pesquisa linear que analisa em média metade dos elementos.

Patrick Oscity
fonte
1
Permitir a duplicação de chaves e o pedido não são benefícios, mas propriedades diferentes. Você precisa escolher a estrutura de dados que se adapta às suas necessidades.
direita
2
Estritamente falando, sim, mas eles podem acabar sendo benefícios se você precisar dessas propriedades - foi isso que eu quis dizer.
Patrick Oscity
@PatrickOscity: Nesse caso, certamente eles seriam mais bem categorizados como requisitos ?
Lightness Races in Orbit
11
@greggreg Há outro benefício implícito em ter listas de palavras-chave: fazemos uma distinção entre dados estruturados e não estruturados. Mapas são extremamente úteis para dados estruturados com um conjunto conhecido de chaves e palavras-chave não são. Hoje, a maior parte do uso de mapas é para dados estruturados e deixamos palavras-chave para dados opcionais. Se tivéssemos mapas, acho que boa parte dessa distinção estaria perdida.
José Valim
1
Na verdade, sim, os mapas são o caminho a percorrer desde 18 de março.
Papipo
12

O principal benefício das listas de palavras-chave é a compatibilidade com versões anteriores do elixir e da base de código erlang existentes.

Eles também adicionam sintaxe de açúcar se usados ​​como argumentos de funções que se assemelham a, por exemplo, uma sintaxe rubi:

def some_fun(arg, opts \\ []), do: ...
some_fun arg, opt1: 1, opt2: 2

A principal desvantagem de usar listas de palavras-chave é que não é possível realizar uma correspondência de padrão parcial nelas:

iex(1)> m = %{a: 1, b: 2}
%{a: 1, b: 2}
iex(2)> %{a: a} = m
%{a: 1, b: 2}
iex(3)> a
1
iex(4)> k = [a: 1, b: 2]
[a: 1, b: 2]
iex(5)> [a: a] = k
** (MatchError) no match of right hand side value: [a: 1, b: 2]

Vamos estendê-lo para argumentos de função. Imagine que precisamos lidar com uma função multicláusula com base no valor de uma das opções:

def fun1(arg, opt1: opt1) when is_nil(opt1), do: do_special_thing
def fun1(arg, opts), do: do_regular_thing

def fun2(arg, %{opt1: opt1}) when is_nil(opt1), do: do_special_thing
def fun2(arg, opts), do: do_regular_thing

Isso nunca executará o do_special_thing:

fun1("arg", opt1: nil, opt2: "some value")
doing regular thing  

Com os argumentos do mapa funcionará:

fun2("arg", %{opt1: nil, opt2: "some value"})
doing special thing
Voldy
fonte
2

Os mapas permitem apenas uma entrada para uma chave específica, enquanto as listas de palavras-chave permitem que a chave seja repetida. Os mapas são eficientes (principalmente à medida que crescem) e podem ser usados ​​na correspondência de padrões do Elixir.

Em geral, use listas de palavras-chave para coisas como parâmetros de linha de comando e para passar opções, e use mapas (ou outra estrutura de dados, o HashDict) quando quiser uma matriz associativa.

Subhash Chandra
fonte