В С# вы можете сделать что-то вроде этого:
public IEnumerable<T> GetItems<T>()
{
for (int i=0; i<10000000; i++) {
yield return i;
}
}
Это возвращает перечислимую последовательность из 10 миллионов целых чисел без выделения коллекции в памяти такой длины.
Есть ли способ сделать что-то подобное в Ruby? Конкретный пример, с которым я пытаюсь разобраться, — это сведение прямоугольного массива в последовательность значений, подлежащих перечислению. Возвращаемое значение не обязательно должно быть Array
или 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 }
В C# любой метод, возвращающий IEnumerable
, который использует yield return
, заставляет компилятор за кулисами генерировать конечный автомат, который обслуживает такое поведение. Я подозреваю, что нечто подобное можно было бы достичь, используя продолжения Ruby, но я не видел примера и сам не совсем понимаю, как это можно сделать.
Действительно кажется возможным использовать для этого Enumerable
. Простым решением для нас будет Array
(который включает модуль Enumerable
), но я не хочу создавать промежуточную коллекцию с N элементами в памяти, когда можно просто предоставить их лениво и вообще избежать скачка памяти.
Если это все еще не имеет смысла, рассмотрите приведенный выше пример кода. get_items
возвращает перечисление, по которому вызывается select
. В select
передается экземпляр, который знает, как предоставить следующий элемент в последовательности всякий раз, когда это необходимо. Важно отметить, что вся коллекция элементов еще не рассчитана. Только когда select
понадобится предмет, он запросит его, и скрытый код в get_items
сработает и предоставит его. Эта лень продолжается по цепочке, так что select
рисует следующий элемент из последовательности только тогда, когда map
запрашивает его. Таким образом, над одним элементом данных за раз может выполняться длинная цепочка операций. Фактически, код, структурированный таким образом, может даже обрабатывать бесконечную последовательность значений без каких-либо ошибок памяти.
Итак, подобная лень легко кодируется на C#, и я не знаю, как это сделать на Ruby.
Надеюсь, так стало понятнее (в будущем я постараюсь не задавать вопросы в 3 часа ночи).