Ответ 1
Возможное объяснение
Одна из целей Enumerator::Lazy
заключается в том, чтобы избежать огромного (или, возможно, бесконечного) массива в памяти. Это может объяснить, почему Enumerator#each
не возвращает желаемый массив.
Вместо того, чтобы рисковать исчерпанием памяти с огромным массивом, методы, такие как Lazy#reject
, предпочитают возвращать nil
в качестве альтернативного значения ( тот, который возвращается после each
):
return lazy_add_method(obj, 0, 0, Qnil, Qnil, &lazy_reject_funcs);
Для сравнения, Enumerable#lazy
возвращает:
VALUE result = lazy_to_enum_i(obj, sym_each, 0, 0, lazyenum_size);
Я подозреваю, что различные аргументы:
-
Qnil
дляreject
-
sym_each
дляlazy
являются причиной:
-
[].lazy.each {}
возвращает[]
-
[].lazy.select{}.each {}
возвращаетnil
.
Тем не менее, для each
не представляется последовательным возвращать массив или nil
.
Альтернативы
каждый
Более сложной альтернативой для вашего кода может быть:
messages = %w(a b c)
messages_to_send = messages.lazy.reject{|x| puts "Is '#{x}' spam?"}
messages_to_send.each{ |m| puts "Send '#{m}'" }
# Is 'a' spam?
# Send 'a'
# Is 'b' spam?
# Send 'b'
# Is 'c' spam?
# Send 'c'
Lazy#reject
возвращает a lazy
Enumerator, поэтому второй message_is_spam?
будет выполнен после первого send_message
.
Есть одна проблема, хотя вызов to_a
в ленивом перечислителе снова вызовет reject
:
sent_messages = messages_to_send.to_a
# Is 'a' spam?
# Is 'b' spam?
# Is 'c' spam?
map
и модифицированный метод
Вы также можете вернуть m
в конце send_message
и использовать Lazy#map
:
sent_messages = messages.lazy.reject { |m| message_is_spam?(m) }
.map { |m| send_message(m) }.to_a
map
должен надежно вернуть нужный объект Enumerator:: Lazy. Вызов Enumerable#to_a
гарантирует, что sent_messages
является массивом.
map
и явный возврат
Если вы не хотите изменять send_message
, вы можете вернуться m
явно в конце каждой map
итерации:
messages = %w(a b c)
sent_messages = messages.lazy.reject{ |m| puts "Is '#{m}' spam?" }
.map{ |m| puts "Send '#{m}'"; m }.to_a
# Is 'a' spam?
# Send 'a'
# Is 'b' spam?
# Send 'b'
# Is 'c' spam?
# Send 'c'
p sent_messages
# ["a", "b", "c"]
Модифицированная логика
Еще одна альтернатива - переопределить логику без lazy
:
sent_messages = messages.map do |m|
next if message_is_spam?(m)
send_message(m)
m
end.compact