关键字列表有什么好处?

时间:2015-01-27 21:29:23

标签: elixir

在灵丹妙药中我们有地图:

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

我们还有关键字列表:

> 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)

两个为什么?

语法?是否因为关键字列表具有更灵活的语法,允许在没有卷曲的情况下定义它们,甚至没有括号作为函数调用的最后一个参数?那为什么不给这个语法糖吗?

重复密钥?是否因为密钥列表可以包含重复密钥?为什么要同时使用Map样式访问和重复键?

效果?是否因为关键字列表具有更好的性能?那为什么要有地图?并且不应该按照关键字查找成员而不是元组列表?

JS Array和Ruby Hash之类的外观?是吗?

我理解结构上它们是不同的数据表示。对我而言,似乎elixir中的关键字列表通过特殊语法(3种不同的句法变体),用例与地图重叠以及不明确的好处使语言复杂化。

使用关键字列表有什么好处?

3 个答案:

答案 0 :(得分:139)

                   ┌──────────────┬────────────┬───────────────────────┐
                   │ 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⁴                 │
└──────────────────┴──────────────┴────────────┴───────────────────────┘

关键字列表是轻量级的,并且在它们下面具有简单的结构,这使得它们非常灵活。您可以将它们视为基于Erlang约定的语法糖,使得在不编写过于丑陋的代码的情况下轻松与Erlang进行交互。例如,关键字列表用于表示函数参数,这是从Erlang继承的属性。在某些情况下,关键字列表是您唯一的选择,特别是如果您需要重复键或订购。它们只是具有与其他替代品不同的特性,这使得它们更适合某些情况而不适合其他情况。

Maps(和Structs)用于存储实际的有效负载数据,因为它们具有基于散列的实现。内部的关键字列表只是每个操作需要遍历的列表,因此它们不具有经典键值数据结构的属性,如常量时间访问。

Elixir还介绍了HashDict作为poor performance of maps at the time it was written的解决方法。但是,从Elixir 1.0.5 / Erlang 18.0和HashDict will be deprecated in future versions开始,此问题已修复。

如果深入研究Erlang标准库,还有更多数据结构存储键/值对:

  • proplists - 类似于Elixir关键字列表
  • maps - 与Elixir地图相同
  • dict - 根据Erlang原语构建的键值字典
  • gb_trees - 一般平衡树

当您需要跨多个进程和/或VM存储键/值对时,您还有以下选项:

  • ets / dets - (基于磁盘)Erlang术语存储
  • mnesia - 分布式数据库

¹一般来说,但当然取决于™。

²最好的情况只是在列表前面。

³适用于Elixir 1.0.5及更高版本,旧版本可能会更慢。

HashDict现已弃用。

⁵需要线性搜索,平均扫描一半的元素。

答案 1 :(得分:8)

关键字列表的主要好处是与现有的elixir和erlang代码库向后兼容。

如果用作类似于例如的函数参数,它们还会添加语法糖。 ruby语法:

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

使用关键字列表的主要缺点是无法对它们执行部分模式匹配:

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]

让我们将它扩展为函数参数。想象一下,我们需要根据其中一个选项的值来处理多重函数:

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

这永远不会执行do_special_thing

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

使用地图参数,它将起作用:

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

答案 2 :(得分:2)

地图仅允许特定键的一个条目,而 关键字列表允许重复键。地图是有效的(特别是随着它们的增长),它们可以 用于Elixir的模式匹配。

通常,使用关键字列表来执行命令行参数和传递选项,并在需要关联数组时使用映射(或其他数据结构,HashDict)。