Reduce starts with the accumulator you pass but then sets it to whatever the block returns.
It might be helpful to see what it does internally (this is not the actual source code but a simple reproduction):
class Array
def my_reduce(memo, &blk)
each { |i| memo = blk.call(memo, i) }
memo
end
end
Here's some examples to show its usage:
# 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]
You can see how only some of these require memo
to be returned as the last line. The ones that don't are taking advantage of methods which return their modified objects, instead of relying on mutability (memo.push
) or local variable assignment (memo += 1)
each_with_object is basically the same thing as reduce except that it automatically returns the accumulator from each block, and reverses the order of the block args (|i, memo|
instead of |memo, i
). It can be nice syntactic sugar for reduce when the memo is a mutable object. Returning the memo from the block is no longer necessary in the following example:
[1,2].each_with_object([]) { |i, memo| memo.push(i + 1) } # => [2,3]
However it won't work with your original example, because the memo (a number) is immutable:
# returns 0 but should return 1
[true, nil].each_with_object(0) { |i, memo| memo += 1 if i }
to say memo += 1
here is nothing but local variable assignment. Remember, you can never change the value of self
for an object in ruby, not even a mutable one. If Ruby had increment operators (i++
) then it might be a different story (see no increment operator in ruby)