阅读文档,Stream.resource
似乎只是为了创建一个可以读取/获取值的资源,而不是写入/放入。我理解正确还是我读错了?如果我理解正确,我必须创建什么类型的资源才能从流中写入/放入Collectable
?
答案 0 :(得分:7)
您已正确阅读文档,Stream.resource
只是一种发布值的便捷方法。如果要使用值,也需要实现Collectable
协议,这也是正确的。您可以查看source code of File.Stream
,它同时实现Enumerable
和Collectable
。
出于演示目的,这里是一个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.Stream
或GenEvent.Stream
实现作为示例。简而言之,假设您有一个MyData
模块,您可以定义一个MyData.stream/0
函数,它将返回一个实现%MyData.Stream{}
和Enumerable
协议的Collectable
结构,允许您提供这两种机制。