Reduce 开始使用您传递的累加器,然后将其设置为块返回的任何内容。
看看它在内部做了什么可能会有所帮助(这不是实际的源代码,而是一个简单的复制品):
class Array
def my_reduce(memo, &blk)
each { |i| memo = blk.call(memo, i) }
memo
end
end
这里有一些例子来展示它的用法:
# with 0 as starting memo
[nil, nil].reduce(0) { |memo, i| i ? memo + 1 : memo } # => 0
[nil, nil].reduce(0) { |memo, i | memo += 1 if i; memo; } # => 0
[nil, nil].reduce(0) { |memo, i| memo + (i ? 1 : 0) } # => 0
# with [] as starting memo
[1,2].reduce([]) { |memo, i| memo.push(i + 1); memo } # => [2,3]
[1,2].reduce([]) { |memo, i| memo.concat([i + 1]) } # => [2,3]
[1,2].reduce([]) { |memo, i| memo + [i + 1] } # => [2,3]
[1,2].reduce([]) { |memo, i| [*memo, i + 1] } # => [2,3]
您可以看到其中只有一些需要 memo 作为最后一行返回。那些没有利用返回其修改对象的方法,而不是依赖可变性(memo.push)或局部变量赋值(memo += 1)
each_with_object 与 reduce 基本相同,只是它自动从每个块返回累加器,并反转块 args 的顺序(|i, memo| 而不是|memo, i)。当备忘录是可变对象时,它可以成为减少的很好的语法糖。在以下示例中不再需要从块中返回备忘录:
[1,2].each_with_object([]) { |i, memo| memo.push(i + 1) } # => [2,3]
但是它不适用于您的原始示例,因为备忘录(数字)是不可变的:
# returns 0 but should return 1
[true, nil].each_with_object(0) { |i, memo| memo += 1 if i }
这里说memo += 1 只不过是局部变量赋值。请记住,您永远不能更改 ruby 中对象的 self 的值,甚至不能更改可变对象。如果 Ruby 有自增运算符 (i++),那么情况可能就不同了(参见 no increment operator in ruby)