查看源代码 可收集 协议 (Elixir v1.16.2)

用于遍历数据结构的协议。

Enum.into/2 函数使用此协议将可枚举对象插入到集合中。

iex> Enum.into([a: 1, b: 2], %{})
%{a: 1, b: 2}

为什么要可收集?

Enumerable 协议对于从集合中取出值很有用。为了支持各种各样的值,由 Enumerable 协议提供的函数不会保留形状。例如,将映射传递给 Enum.map/2 始终返回列表。

这种设计是有意的。 Enumerable 旨在支持无限集合、资源和其他具有固定形状的结构。例如,将值插入到 Range 中没有意义,因为它具有固定的形状,其中只存储了范围限制和步长。

Collectable 模块旨在填补 Enumerable 协议留下的空白。 Collectable.into/1 可以看作是 Enumerable.reduce/3 的反面。如果 Enumerable 中的函数是关于取出值的,那么 Collectable.into/1 就是关于将这些值收集到一个结构中。

例子

为了展示如何手动使用 Collectable 协议,让我们玩一下对 MapSet 的简化实现。

iex> {initial_acc, collector_fun} = Collectable.into(MapSet.new())
iex> updated_acc = Enum.reduce([1, 2, 3], initial_acc, fn elem, acc ->
...>   collector_fun.(acc, {:cont, elem})
...> end)
iex> collector_fun.(updated_acc, :done)
MapSet.new([1, 2, 3])

为了展示如何实现该协议,我们可以再次看看对 MapSet 的简化实现。在这个实现中,“收集”元素只是意味着通过 MapSet.put/2 将它们插入到集合中。

defimpl Collectable, for: MapSet do
  def into(map_set) do
    collector_fun = fn
      map_set_acc, {:cont, elem} ->
        MapSet.put(map_set_acc, elem)

      map_set_acc, :done ->
        map_set_acc

      _map_set_acc, :halt ->
        :ok
    end

    initial_acc = map_set

    {initial_acc, collector_fun}
  end
end

现在我们可以调用 Enum.into/2

iex> Enum.into([1, 2, 3], MapSet.new())
MapSet.new([1, 2, 3])

总结

类型

t()

实现此协议的所有类型。

函数

返回一个初始累加器和一个“收集器”函数。

类型

@type command() :: {:cont, term()} | :done | :halt
@type t() :: term()

实现此协议的所有类型。

函数

@spec into(t()) ::
  {initial_acc :: term(), collector :: (term(), command() -> t() | term())}

返回一个初始累加器和一个“收集器”函数。

接收一个 collectable,它可以用作将传递给该函数的初始累加器。

收集器函数接收一个项和一个命令,并在每个 {:cont, term} 命令中将该项注入到可收集的累加器中。

当不再注入值时,:done 将作为命令传递。这在需要关闭资源或规范化值时很有用。当命令为 :done 时,必须返回可收集对象。

如果注入突然中断,则传递 :halt,并且该函数可以返回任何值,因为它不会被使用。

有关如何使用 Collectable 协议和 into/1 的示例,请参阅模块文档。