为什么Elixir中的元组不可枚举?

raa*_*cer 7 arrays performance tuples enumerable elixir

我需要一个有效的结构,用于数千个相同类型的元素的数组,能够进行随机访问.

虽然列表在迭代和前置时效率最高,但随机访问速度太慢,因此不符合我的需求.

地图效果更好.Howerver它会导致一些开销,因为它用于键值可能是任何东西的键值对,而我需要一个索引从0到N的数组.因此我的应用程序对于地图工作太慢.我认为这对于处理具有随机访问的有序列表这样的简单任务来说是不可接受的开销.

我发现元组是Elixir中最有效的结构,用于我的任务.与我的机器上的地图相比,它更快

  1. 在迭代中 - 1_000为1.02x,1_000_000元素为1.13x
  2. 随机访问 - 1.68x代表1_000,2.48x代表1_000_000
  3. 和复制 - 1.82x为1_000,6.37x为1_000_000.

因此,我的元组代码比地图上的相同代码快5倍.它可能不需要解释为什么元组比map更有效.目标已经实现,但是每个人都告诉"不要使用元组来获取类似元素的列表",没有人可以解释这个规则(这种情况的例子 /sf/answers/2183522631/).

顺便说一句,Python中有元组.它们也是不可变的,但仍然是可迭代的.

所以,

1.为什么 Elixir中的元组不可枚举?有任何技术或逻辑限制吗?

2.为什么我不应该把它们作为相似的元素列表?有什么缺点吗?

请注意:问题是"为什么",而不是"如何".上面的解释只是一个例子,其中元组比列表和映射更好.

raa*_*cer 7

1.不为元组实现Enumerable的原因

退休的 Elixir谈话邮件列表中:

如果有元组的协议实现,它将与所有记录冲突.鉴于协议的自定义实例几乎总是为记录定义,添加元组会使整个Enumerable协议变得毫无用处.

- 彼得明顿

我希望元组最初可以枚举,甚至最终在它们上实现了Enumerable,但这些都没有用.

- 克里斯基尔

这如何破坏协议?我会尝试把事情放在一起,从技术角度解释问题.

元组.关于元组的有趣之处在于它们主要用于使用模式匹配的一种鸭子打字.每次需要一些新的简单类型时,不需要为新结构创建新模块.而不是这个你创建一个元组 - 一种虚拟类型的对象.原子通常被用作第一元件类型的名称,例如和.这就是Elixir几乎在任何地方使用元组的方式,因为这是他们设计的目的.它们也被用作来自Erlang的" 记录 " 的基础.Elixir已为此目的进行了结构化,但它还提供了与Erlang兼容的模块Record.因此,在大多数情况下,元组表示异构数据的单个结构,这些结构不应被列举.应该将元组视为各种虚拟类型的实例.甚至有指令允许基于元组定义自定义类型.但请记住它们是虚拟的,并且仍然对所有这些元组都返回true.{:ok, result}{:error, description}@typeis_tuple/1

协议.另一方面,Elixir中的协议是一种提供ad hoc多态性类型类.对于那些来自OOP的人来说,这类似于超类和多重继承.协议为您做的一件重要事情是自动类型检查.将某些数据传递给协议函数时,它会检查数据是否属于此类,即该协议是针对此数据类型实现的.如果没有,那么你会得到这样的错误:

** (Protocol.UndefinedError) protocol Enumerable not implemented for {}
Run Code Online (Sandbox Code Playgroud)

这样,除非您做出错误的架构决策,否则Elixir会将您的代码保存为愚蠢的错误和复杂的错误

共.现在假设我们为元组实现了Enumerable.它的作用是让所有元组都可以枚举,而Elixir中99.9%的元组并不是这样的.所有检查都被打破了.悲剧就像世界上所有动物都开始嘎嘎叫一样.如果元组意外地传递给Enum或Stream模块,那么您将看不到有用的错误消息.而不是这样,您的代码将产生意外的结果,不可预测的行为以及可能的数据损坏.

2.不使用元组作为集合的原因

好稳健的药剂代码应该包含typespecs,帮助开发人员了解代码,并给透析器来检查代码对你的能力.想象一下,你想要一个类似元素的集合.列表和地图的typespec可能如下所示:

@type list_of_type :: [type]
@type map_of_type :: %{optional(key_type) => value_type}
Run Code Online (Sandbox Code Playgroud)

但是你不能为元组编写相同的typespec,因为它{type}意味着"一个单元素类型的元组type".您可以为预定义长度的元组编写typespec,{type, type, type}或者为任何元素的元组tuple()编写,但是没有办法仅仅通过设计为类似元素的元组编写typespec.所以选择元组来存储你的elemenets集合意味着你失去了一个很好的能力来使你的代码健壮.

结论

不使用元组作为类似元素列表的规则是一个经验法则,解释了在大多数情况下如何在Elixir中选择正确的类型.违反此规则可能被视为不良设计选择的可能信号.当人们说"元组不是用于设计的集合"时,这意味着不仅"你做了一些不寻常的事情",而且"你可以通过在你的应用程序中做错设计来打破Elixir功能".

如果你真的想因为某种原因想要使用元组作为集合而且你确定你知道你做了什么,那么把它包装成一些结构是个好主意.您可以为您的结构实现Enumerable协议,而没有风险来破坏元组周围的所有内容.这值得大家注意的是二郎使用元组作为集合为内部表示array,gb_trees,gb_sets,等.

iex(1)> :array.from_list ['a', 'b', 'c']
{:array, 3, 10, :undefined,
 {'a', 'b', 'c', :undefined, :undefined, :undefined, :undefined, :undefined,
  :undefined, :undefined}}
Run Code Online (Sandbox Code Playgroud)

不确定是否有任何其他技术原因不使用元组作为集合.如果有人可以为记录和可枚举协议之间的冲突提供另一个很好的解释,欢迎他改进这个答案.