关键字列表有什么好处?

What is the benefit of Keyword Lists?

在长生不老药中我们有地图:

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

为什么两者都有?

语法? 是因为关键字列表具有更灵活的语法,允许在定义它们时不使用大括号,甚至不使用方括号作为函数调用的最后一个参数吗?那为什么不给地图这个语法糖呢?

重复键?是因为关键字列表可以有重复键吗?为什么您需要地图样式访问和重复键?

性能?是因为关键字列表的性能更好吗?那为什么要有地图呢?在按键查找成员方面,映射不应该比元组列表更高效吗?

JS数组和Ruby哈希一样的外观?是吗?

我了解它们在结构上是不同的数据表示形式。在我看来,elixir 中的关键字列表似乎通过特殊的语法(3 种不同的语法变体)、用例与映射重叠以及不明确的好处使语言复杂化。

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

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⁴
Performance¹ (Access) slow⁵ fast³ fast⁴

关键字列表是轻量级的,并且在它们下面有一个简单的结构,这使得它们非常灵活。您可以将它们视为 Erlang 约定之上的语法糖,使与 Erlang 的交互变得容易,而无需编写太难看的代码。例如,关键字列表用于表示函数参数,这是从 Erlang 继承的 属性。在某些情况下,关键字列表是您唯一的选择,尤其是当您需要重复键或排序时。它们只是具有与其他替代品不同的属性,这使得它们更适合某些情况而不太适合其他情况。

映射(和结构)用于存储实际有效负载数据,因为它们具有基于散列的实现。关键字列表内部只是每次操作需要遍历的列表,所以不具备常量访问等经典键值数据结构的特性。

Elixir 还引入了 HashDict 作为 poor performance of maps at the time it was written. However, this is fixed now as of Elixir 1.0.5/Erlang 18.0 and HashDict will be deprecated in future versions.

的解决方法

如果你深入研究 Erlang 标准库,存储 key/value 对的数据结构甚至更多:

  • proplists – 类似于 Elixir 关键字列表
  • maps – 与 Elixir 地图相同
  • dict – 从 Erlang 基元构建的键值字典
  • gb_trees – 一般平衡树

当您需要跨多个进程 and/or 虚拟机存储 key/value 对时,您也可以使用这些选项:

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

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

² 最好的情况就是添加到列表中。

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

HashDict 现已弃用。

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

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

一般来说,使用关键字列表来处理命令行参数和传递选项等内容,并在需要关联数组时使用映射(或其他数据结构,HashDict)。

关键字列表的主要好处是向后兼容现有的 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