Inconsistency in Enumerable#reduce without initial value

Can someone please explain this inconsistency:

 {"a" => "b"}.reduce { raise }
 #=> ["a", "b"]

 {"a" => "b"}.reduce(nil) { raise }
 #=> RuntimeError

 {"a" => "b", "c" => "d"}.reduce { raise }
 #=> RuntimeError

Why doesn’t the first form enter the block?

I found this when trying to do something like this:

 {"a" => "b"}.reduce { |memo, (k, v)| v }
 #=> ["a", "b"]

I expected the return value “b”.

Andrew V.

IMHO perhaps #reduce needs at least two values to do his job!
It needs to do some operation with the previous value and the next one.
Be it the first and the second value, or the parameter and the first
value.
So it doesn’t even bother to yield the block.

{“a” => “b”}.reduce { |prv, nxt| puts “prv = #{prv} - nxt = #{nxt}” }
{“a” => “b”}.reduce(“MyParameter”) { |prv, nxt| puts “prv = #{prv} -
nxt = #{nxt}” }
{“a” => “b”, “c” => “d”}.reduce { |prv, nxt| puts “prv = #{prv} - nxt
= #{nxt}” }

Abinoam Jr.
@abinoamjr_en

On 14-05-30, 13:49, Abinoam Jr. wrote:

IMHO perhaps #reduce needs at least two values to do his job!
It needs to do some operation with the previous value and the next one.

Ah, that seems to make sense, thanks. It still seems surprising that it
doesn’t yield the block in only that case. I do wonder if this is
intended, or an oversight.

Andrew V.

On 14-05-30, 16:13, Matthew K. wrote:

Intentional. The function in #reduce is binary (2 in => 1 out), but when
there is only one item being reduced (i.e. an empty list & an initial
memo, or a single-item list & no memo) what would you pass to the binary
function?

Right on. When I thought about it some more I realized the same thing.

Also: reducing a list of one to a single value is trivial :wink:

Of course, unless you’re expecting the block to do some transformation
to each value when accumulating it… which was my issue.

Andrew V.

On May 31, 2014 8:33 AM, “Andrew V.” [email protected] wrote:

On 14-05-30, 13:49, Abinoam Jr. wrote:

IMHO perhaps #reduce needs at least two values to do his job!
It needs to do some operation with the previous value and the next one.

Ah, that seems to make sense, thanks. It still seems surprising that it
doesn’t yield the block in only that case. I do wonder if this is
intended,
or an oversight.

Andrew V.

Intentional. The function in #reduce is binary (2 in => 1 out), but when
there is only one item being reduced (i.e. an empty list & an initial
memo,
or a single-item list & no memo) what would you pass to the binary
function?

Also: reducing a list of one to a single value is trivial :wink:

Forgot to mention:

On May 31, 2014 4:27 AM, “Andrew V.” [email protected] wrote:

{"a" => "b"}.reduce { |memo, (k, v)| v }
#=> ["a", "b"]

I think you want ´hsh.map{|k,v|v}.reduce{|m,e|e}´ or
´hsh.reduce{|m,e|e}.map{|k,v|v}´

You can’t do them in one step with the current list, because map and
reduce
are fundamentally different.

Or you could change the list so it fits the reduce contract, and use:
´hsh.reduce(nil){|m,(k,v)|v}´

Incidentally, they all return nil for an empty hash.

Andrew V. wrote in post #1147683:

On 14-05-30, 13:49, Abinoam Jr. wrote:

IMHO perhaps #reduce needs at least two values to do his job!
It needs to do some operation with the previous value and the next one.

Ah, that seems to make sense, thanks. It still seems surprising that it
doesn’t yield the block in only that case. I do wonder if this is
intended, or an oversight.

Andrew V.

I don’t think there is an oversight. As Abinoam Jr. already mentioned
#reduce `combines elements’ and if there is only one element, it has
nothing to combine with.

In your first example there is only one element [“a”, “b”] and no
accumulator.

In the second, you are passing an initial value of accumulator so
#reduce has to combine it with the first and only element [“a”, “b”], so
block gets invoked.

I don’t see any inconsistency here.

On May 30, 2014, at 16:27, Matthew K. [email protected] wrote:

I think you want hsh.map{|k,v|v}.reduce{|m,e|e} or
hsh.reduce{|m,e|e}.map{|k,v|v}

Or just say what you’re actually trying to do and call #keys and do
something with that.

Same annoyance as people writing ary.length == 0 instead of
ary.empty?. Clearer intent always wins.

On Sat, May 31, 2014 at 1:22 AM, Andrew V. [email protected] wrote:

On 14-05-30, 16:13, Matthew K. wrote:

Intentional. The function in #reduce is binary (2 in => 1 out), but when
there is only one item being reduced (i.e. an empty list & an initial
memo, or a single-item list & no memo) what would you pass to the binary
function?

Right on. When I thought about it some more I realized the same thing.

There is a distinction between invoking #reduce or #inject without
arguments and with one argument. A structured test will convey that:

irb(main):006:0> 4.times {|i| a=i.times.to_a; printf “%d %p\n”,i,a;
a.reduce {|*x| printf “>> %p\n”, x;nil}}
0 []
1 [0]
2 [0, 1]

[0, 1]
3 [0, 1, 2]
[0, 1]
[nil, 2]
=> 4
irb(main):007:0> 4.times {|i| a=i.times.to_a; printf “%d %p\n”,i,a;
a.reduce(99) {|*x| printf “>> %p\n”, x;nil}}
0 []
1 [0]
[99, 0]
2 [0, 1]
[99, 0]
[nil, 1]
3 [0, 1, 2]
[99, 0]
[nil, 1]
[nil, 2]
=> 4

Kind regards

robert