在 C# 中,您可以执行以下操作:
public IEnumerable<T> GetItems<T>()
{
for (int i=0; i<10000000; i++) {
yield return i;
}
}
这将返回一个包含 1000 万个整数的可枚举序列,而无需在内存中分配该长度的集合。
有没有办法在 Ruby 中做同样的事情?我要处理的具体示例是将矩形数组展平为要枚举的值序列。返回值不必是Array
or Set
,而是某种只能按顺序迭代/枚举的序列,而不是按索引。因此,整个序列不需要同时在内存中分配。在 .NET 中,这是IEnumerable
和IEnumerable<T>
.
对 Ruby 世界中使用的术语进行任何澄清都会有所帮助,因为我更熟悉 .NET 术语。
编辑
也许我最初的问题还不够清楚——我认为yield
在 C# 和 Ruby 中具有非常不同含义的事实是造成混淆的原因。
我不想要一个需要我的方法使用块的解决方案。我想要一个具有实际返回值的解决方案。返回值允许对序列进行方便的处理(过滤、投影、连接、压缩等)。
这是我如何使用的一个简单示例get_items
:
things = obj.get_items.select { |i| !i.thing.nil? }.map { |i| i.thing }
In C#, any method returning IEnumerable
that uses a yield return
causes the compiler to generate a finite state machine behind the scenes that caters for this behaviour. I suspect something similar could be achieved using Ruby's continuations, but I haven't seen an example and am not quite clear myself on how this would be done.
It does indeed seem possible that I might use Enumerable
to achieve this. A simple solution would be to us an Array
(which includes module Enumerable
), but I do not want to create an intermediate collection with N items in memory when it's possible to just provide them lazily and avoid any memory spike at all.
If this still doesn't make sense, then consider the above code example. get_items
returns an enumeration, upon which select
is called. What is passed to select
is an instance that knows how to provide the next item in the sequence whenever it is needed. Importantly, the whole collection of items hasn't been calculated yet. Only when select
needs an item will it ask for it, and the latent code in get_items
will kick into action and provide it. This laziness carries along the chain, such that select
only draws the next item from the sequence when map
asks for it. As such, a long chain of operations can be performed on one data item at a time. In fact, code structured in this way can even process an infinite sequence of values without any kinds of memory errors.
So, this kind of laziness is easily coded in C#, and I don't know how to do it in Ruby.
I hope that's clearer (I'll try to avoid writing questions at 3AM in future.)