【问题标题】:How elixir keep the accumulator灵药如何保持蓄能器
【发布时间】:2016-02-21 11:17:50
【问题描述】:

我正在尝试了解灵药流。
首先,我有一个列表,它遍历一个范围和多个 2。

iex(5)>   stream = 1..3 |>
...(5)>   Enum.map(&IO.inspect(&1)) |>
...(5)>   Enum.map(&(&1 * 2)) |>
...(5)>   Enum.map(&IO.inspect(&1)) |>
...(5)>   Enum.reduce(4, &+/2)
1
2
3
2
4
6
16

这里一切都清楚了,每个枚举都返回一个列表以进一步处理该列表。它为列表中的每个元素迭代四次。
让我们用流构建它:

iex(4)>   stream = 1..3 |>
...(4)>   Stream.map(&IO.inspect(&1)) |>
...(4)>   Stream.map(&(&1 * 2)) |>
...(4)>   Stream.map(&IO.inspect(&1)) |>
...(4)>   Enum.reduce(4, &+/2)
1
2
2
4
3
6
16

这里它只对范围内的每个数字进行一次迭代,这与上面的 enum 示例不同。我不明白的是,当 Enum.reduce 的累加器只迭代一次时,它如何保持值并由他们自己进行递归调用?
当我按如下方式迭代枚举时:

iex(25)> f = fn(x, y) ->
...(25)> IO.puts(y)
...(25)> x + y
...(25)> end
#Function<12.54118792/2 in :erl_eval.expr/5>
iex(26)> iter = 1..5 |> Enum.reduce(4, f)
4
5
7
10
14
19

然后我可以想象,enum.reduce 如何将累加器传递给下一个递归调用。

但是,如果流的迭代只执行一次,流如何通过累加器进行下一次递归调用?

【问题讨论】:

标签: elixir


【解决方案1】:

不确定您缺少/询问的是什么,但让我尝试解释一下。首先,你必须知道流是如何工作的以及在 Elixir 中是如何表示的:流只是一组用于操作可枚举的函数。当您编写流(例如,1..3 |&gt; Stream.map(...) |&gt; Stream.map(...))时,您并没有对流执行任何“具体”操作,您只是将函数添加到将用于处理初始可枚举的函数列表中。当使用流调用 Enum.reduce/3 时,即开始迭代可枚举(一次一个元素)。

也可以通过检查Enum.reduce/3 的进度来更好地理解您的示例:

iex> printer1 = fn el -> IO.puts "1st iteration, el: #{inspect el}"; el end
iex> printer2 = fn el -> IO.puts "2nd iteration, el: #{inspect el}"; el end
iex> reducer = fn el, acc ->
...>   IO.puts "reducing, el: #{inspect el}, acc: #{inspect acc}\n"
...>   el + acc
...> end
iex> 1..3 |> Stream.map(printer1) |> Stream.map(&(&1 + 10)) |> Stream.map(printer2) |> Enum.reduce(0, reducer)
1st iteration, el: 1
2nd iteration, el: 11
reducing, el: 11, acc: 0

1st iteration, el: 2
2nd iteration, el: 12
reducing, el: 12, acc: 11

1st iteration, el: 3
2nd iteration, el: 13
reducing, el: 13, acc: 23

36

如您所见,发生了一些小奇迹:当我们调用 Enum.reduce/3 时,流开始展开,我们遍历映射三个函数的每个元素,并在其上调用 reducer 和累加器。

它可以帮助您研究Enumerable 协议的文档,因为该协议用于从流中获取元素(通常一次一个)。当您链接Enum.map/2 操作时(就像您在第一个示例中所做的那样),Enum.reduce/3 将接收一个列表作为其第一个参数,因此将使用列表的Enumerable 协议:它的实现只是迭代列表和“将列表中的元素“生成”到Enumerable.reduce,一次一个。当您链接 Stream.map/2 操作时,Enum.reduce/3 将接收一个流,因此将使用流的 Enumerable 协议:它的实现将每个元素从原始可枚举 (1..3) 中取出,应用流操作 ( Stream.map/2 在这种情况下的链),并一次产生一个结果。

希望我已经让您更清楚了,如果您仍有疑问,请提出任何问题:)。

【讨论】:

  • 流一个一个传值时,没有指向下一个值的指针。当我只使用Enum.map 时,它将作为一个列表的中间,并且该列表将传递到下一个Enum.map,该列表总是有一个指向下一个值的指针。当我使用流时,只有一个值会通过,Enum.map 如何知道下一个值的指针?你知道我的意思吗?
  • @zero_coding 不,我不确定你的意思:|如果我正确理解您的疑问,您不确定流如何跟踪它在可枚举中的位置。我的建议(如答案中所述)是研究Enumerable 协议:我们“懒惰”地实现枚举(一次一个元素)的方式是“产生”枚举的一个元素和一个延续函数将递归地产生并返回一个延续,依此类推。在Enumerable 的文档中查找与:suspend 相关的内容。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-21
  • 2016-01-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-12
相关资源
最近更新 更多