我有一段代码如下:
sent_messages = messages.lazy.reject { |m| message_is_spam?(m) }
.each { |m| send_message(m) }
# Do something with sent_messages...
Run Code Online (Sandbox Code Playgroud)
某些上下文:message_is_spam?
如果邮件的收件人在最近5分钟内收到消息,则该方法返回true.当messages
包含同一收件人的多条消息时,后一条消息将仅在发送第一条消息后被视为垃圾邮件.为了确保后一条消息被视为垃圾邮件,我懒惰地拒绝垃圾邮件并发送它们.
我希望.each
返回一个包含所有项目的数组,但我得到了nil
..each
总是返回一个数组,除了在这一个场景中:
[].each {} # => []
[].lazy.each {} # => []
[].select {}.each {} # => []
[].lazy.select {}.each {} # => nil
Run Code Online (Sandbox Code Playgroud)
为了增加混淆,JRuby返回[]
上面的所有示例.
为什么这样.each
调用时返回nil?我在文档中找不到任何关于它的内容,很难弄清楚C代码中发生了什么.
我已经找到了彻底绕过这个问题的方法; 如果我为每个收件人(messages.uniq_by(&:recipient)
)选择最多1条消息,则该操作不再需要延迟.尽管如此,这仍然让我感到惊讶.
目的之一Enumerator::Lazy
是避免内存中出现巨大(或可能无限)的数组。这可以解释为什么Enumerator#each
不返回所需的数组。
Lazy#reject
类似的方法更喜欢返回nil
作为替代值(之后返回的值each
),而不是冒着用一个巨大的数组耗尽内存的风险:
return lazy_add_method(obj, 0, 0, Qnil, Qnil, &lazy_reject_funcs);
Run Code Online (Sandbox Code Playgroud)
相比之下,Enumerable#lazy
返回:
VALUE result = lazy_to_enum_i(obj, sym_each, 0, 0, lazyenum_size);
Run Code Online (Sandbox Code Playgroud)
我怀疑不同的论点:
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'
Run Code Online (Sandbox Code Playgroud)
Lazy#reject
返回一个Lazy
枚举器,因此第二个message_is_spam?
将在第一个之后执行send_message
。
但有一个问题,调用to_a
惰性枚举器将reject
再次调用:
sent_messages = messages_to_send.to_a
# Is 'a' spam?
# Is 'b' spam?
# Is 'c' spam?
Run Code Online (Sandbox Code Playgroud)
map
和修改方法您还可以m
在末尾返回send_message
并使用Lazy#map
:
sent_messages = messages.lazy.reject { |m| message_is_spam?(m) }
.map { |m| send_message(m) }.to_a
Run Code Online (Sandbox Code Playgroud)
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"]
Run Code Online (Sandbox Code Playgroud)
另一种选择是重新定义您的逻辑,而不需要lazy
:
sent_messages = messages.map do |m|
next if message_is_spam?(m)
send_message(m)
m
end.compact
Run Code Online (Sandbox Code Playgroud)