Stream.resource只是来自,而不是?

时间:2014-12-11 23:12:24

标签: elixir

阅读文档,Stream.resource似乎只是为了创建一个可以读取/获取值的资源,而不是写入/放入。我理解正确还是我读错了?如果我理解正确,我必须创建什么类型的资源才能从流中写入/放入Collectable

2 个答案:

答案 0 :(得分:7)

您已正确阅读文档,Stream.resource只是一种发布值的便捷方法。如果要使用值,也需要实现Collectable协议,这也是正确的。您可以查看source code of File.Stream,它同时实现EnumerableCollectable

出于演示目的,这里是一个ChunkedWriter模块,它存储值直到缓冲区已满,然后在达到限制时刷新它:

defmodule ChunkedWriter do
  def open(chunk_size) do
    Agent.start_link fn -> {[], chunk_size} end
  end

  def write(agent, value) do
    Agent.update agent, fn {old_buffer, chunk_size} ->
      buffer = [value | old_buffer]
      new_buffer = cond do
        length(buffer) < chunk_size -> buffer
        true -> do_flush(buffer)
      end
      {new_buffer, chunk_size}
    end
  end

  def flush(agent) do
    Agent.update agent, fn {buffer, chunk_size} ->
      {do_flush(buffer), chunk_size}
    end
  end

  defp do_flush(buffer) do
    buffer |> Enum.reverse |> Enum.each(&IO.puts/1)
    IO.puts "---"
    []
  end

  def close(agent) do
    flush(agent)
    Agent.stop(agent)
  end

  def stream(chunk_size) do
    %ChunkedWriter.Stream{chunk_size: chunk_size}
  end
end

这个模块的用法如下:

writer = ChunkedWriter.open(3)

ChunkedWriter.write(writer, 1)
ChunkedWriter.write(writer, 2)
ChunkedWriter.write(writer, 3)
ChunkedWriter.write(writer, 4)
ChunkedWriter.write(writer, 5)

ChunkedWriter.close(writer)

此输出

1
2
3
---
4
5
---

现在ChunkedWriter.stream/1方法只是设置一个结构,然后将其分派到ChunkedWriter.Stream。以下是ChunkedWriter.Stream模块及其Collectable实现,因此我们可以将Enumerable输入其中。

defmodule ChunkedWriter.Stream do
  defstruct chunk_size: 1

  defimpl Collectable do
    def into(stream = %ChunkedWriter.Stream{chunk_size: chunk_size}) do
      {:ok, writer} = ChunkedWriter.open(chunk_size)
      {stream, fn
        _acc, {:cont, value} ->
          ChunkedWriter.write(writer, value)
        _acc, :done ->
          :ok = ChunkedWriter.close(writer)
          stream
        _, :halt ->
          :ok = ChunkedWriter.close(writer)
      end}
    end
  end
end

行动中:

Stream.cycle([1,2,3])
|> Stream.take(10)
|> Stream.into(ChunkedWriter.stream(4))
|> Stream.run

打印:

1
2
3
1
---
2
3
1
2
---
3
1
---

答案 1 :(得分:4)

你是对的。 Stream.resource / 3是一种定义源的便利。在下面,Stream.resource / 3实现了一个Enumerable,它是所有源(以及像map这样的变换器)实现的协议。

Enumerable协议非常适合从事物中取出值并将它们传递给它们,但它并不能将这些值收集到合理的事物中。为此,您要实施Collectable协议。

嗯,你可能想知道,如果你想要两者怎么办?

您可以查看Elixir代码中的File.StreamGenEvent.Stream实现作为示例。简而言之,假设您有一个MyData模块,您可以定义一个MyData.stream/0函数,它将返回一个实现%MyData.Stream{}Enumerable协议的Collectable结构,允许您提供这两种机制。