Unexpected behavior in inject

A

Alex Fort

[Note: parts of this message were removed to make it a legal post.]

I ran across something that puzzled me today, and I thought I'd ask here and
see what you guys/girls think. I'm by no means a ruby guru, so if this is
obvious to everyone but me, I'll take the learning experience.


Here's the simple script I was testing out:

[1,2,3].inject(0) do |acc, num|
acc + num
end

It works perfectly fine when I run it. No surprises here. However, when I
add a puts call after the accumulator addition, like so:

[1,2,3].inject(0) do |acc, num|
acc + num
puts acc
end


Ruby gives me this error:

bash ~ $ ruby test.rb
0
test.rb:2: undefined method `+' for nil:NilClass (NoMethodError)
from test.rb:5:in `inject'
from test.rb:1:in `each'
from test.rb:1:in `inject'
from test.rb:1



This seems pretty strange to me, as if puts is modifying the accumulator by
resetting it to nil. Is this normal behavior? If so, I would greatly
appreciate it if someone could point me in the right direction as to why
this is the way it is. I also noticed that if I place the puts call *before*
the addition, there is no error, which also strikes me as a little odd.
Here's my ruby version (using cygwin on windows server 2003):

bash ~ $ ruby -v
ruby 1.8.6 (2007-09-24 patchlevel 111) [i386-mswin32]


Thanks,
Alex
 
J

Jonathan Nielsen

[Note: parts of this message were removed to make it a legal post.]
[1,2,3].inject(0) do |acc, num|
acc + num
puts acc
end


Ruby gives me this error:

bash ~ $ ruby test.rb
0
test.rb:2: undefined method `+' for nil:NilClass (NoMethodError)
from test.rb:5:in `inject'
from test.rb:1:in `each'
from test.rb:1:in `inject'
from test.rb:1

This seems pretty strange to me, as if puts is modifying the accumulator by
resetting it to nil. Is this normal behavior? If so, I would greatly
appreciate it if someone could point me in the right direction as to why
this is the way it is.


Yes, this is exactly as expected. The last statement you put in the block
is the return value of the block. 'puts' always returns nil, so the next
iteration acc is nil.

To get the behavior you are looking for:

[1,2,3].inject(0) do |acc, num|
sum = acc + num
puts sum
sum
end

Now since 'sum' is the final statement in the block, acc will be sum for the
next iteration.

-Jonathan Nielsen
 
J

Josh Cheek

[Note: parts of this message were removed to make it a legal post.]

I ran across something that puzzled me today, and I thought I'd ask here
and
see what you guys/girls think. I'm by no means a ruby guru, so if this is
obvious to everyone but me, I'll take the learning experience.


Here's the simple script I was testing out:

[1,2,3].inject(0) do |acc, num|
acc + num
end
Let me give you a slightly different example to show how it works: You pass
in the zero, and it goes into acc in the block. Whatever the block returns
is then fed back into acc, for the next iteration. After the last iteration,
whatever the block returns is returned by the inject method. I think of it
as a number passing through an Enumerable and coming out the other side
modified.

(5..10).inject 0 do |sum,num|
sum + num
end

This should return 45 in the following manner:
The first time the block is passed 0 , 5 and it returns 5
The second time the block is passed 5 , 6 and it returns 11
The third time the block is passed 11 , 7 and it returns 18
The fourth time the block is passed 18 , 8 and it returns 26
The fourth time the block is passed 26 , 9 and it returns 35
The fourth time the block is passed 35 , 10 and it returns 45
The method then returns 45



So why doesn't this work?

[1,2,3].inject(0) do |acc, num|
acc + num
puts acc
end

Because the puts method returns nil. It is also the last thing in the block,
so the block returns nil. So nil is fed back into acc. Then nil + acc raises
the NoMethodError.

To resolve this, think about where you could put the puts method that won't
cause the block to return nil.


-----

If you are interested, here is a functional version (takes the enumerable as
a parameter) of inject that I wrote, except I call ti "passthrough" because
that is more meaningful for me.

def passthrough( enumerable , to_pass )
enumerable.each do |element|
to_pass = yield to_pass , element
end
to_pass
end
 
A

Alex Fort

[1,2,3].inject(0) do |acc, num|
=A0acc + num
=A0puts acc
end


Ruby gives me this error:

bash ~ $ ruby test.rb
0
test.rb:2: undefined method `+' for nil:NilClass (NoMethodError)
=A0 =A0 =A0 =A0from test.rb:5:in `inject'
=A0 =A0 =A0 =A0from test.rb:1:in `each'
=A0 =A0 =A0 =A0from test.rb:1:in `inject'
=A0 =A0 =A0 =A0from test.rb:1

This seems pretty strange to me, as if puts is modifying the accumulator= by
resetting it to nil. Is this normal behavior? If so, I would greatly
appreciate it if someone could point me in the right direction as to why
this is the way it is.


Yes, this is exactly as expected. =A0The last statement you put in the bl= ock
is the return value of the block. =A0'puts' always returns nil, so the ne= xt
iteration acc is nil.

To get the behavior you are looking for:

[1,2,3].inject(0) do |acc, num|
=A0sum =3D acc + num
=A0puts sum
=A0sum
end

Now since 'sum' is the final statement in the block, acc will be sum for = the
next iteration.

I understand now. Thank you for clarifying that for me.


Alex
 
A

Alex Fort

I ran across something that puzzled me today, and I thought I'd ask here
and
see what you guys/girls think. I'm by no means a ruby guru, so if this i= s
obvious to everyone but me, I'll take the learning experience.


Here's the simple script I was testing out:

[1,2,3].inject(0) do |acc, num|
=A0acc + num
end
Let me give you a slightly different example to show how it works: =A0You= pass
in the zero, and it goes into acc in the block. Whatever the block return= s
is then fed back into acc, for the next iteration. After the last iterati= on,
whatever the block returns is returned by the inject method. I think of i= t
as a number passing through an Enumerable and coming out the other side
modified.

(5..10).inject 0 do |sum,num|
=A0sum + num
end

This should return 45 in the following manner:
The first time the block is passed 0 , 5 and it returns 5
The second time the block is passed 5 , 6 and it returns 11
The third time the block is passed 11 , 7 and it returns 18
The fourth time the block is passed 18 , 8 and it returns 26
The fourth time the block is passed 26 , 9 and it returns 35
The fourth time the block is passed 35 , 10 and it returns 45
The method then returns 45



So why doesn't this work?

[1,2,3].inject(0) do |acc, num|
=A0acc + num
=A0puts acc
end

Because the puts method returns nil. It is also the last thing in the blo= ck,
so the block returns nil. So nil is fed back into acc. Then nil + acc rai= ses
the NoMethodError.

To resolve this, think about where you could put the puts method that won= 't
cause the block to return nil.

That's where I was going wrong, I didn't realize that inject passed
the value of the *block*, back to the accumulator, not just the
modified accumulator. I was thinking that ruby was somehow detecting
when I modified the accumulator, so I was sometimes writing code like
this:

[1,2,3].inject(0) {|acc, num| acc +=3D num}

Now I get it. It's all about the value of the block, and there's
really no magic going on. Insight is a wonderful thing :)

-----

If you are interested, here is a functional version (takes the enumerable= as
a parameter) of inject that I wrote, except I call ti "passthrough" becau= se
that is more meaningful for me.

def passthrough( enumerable , to_pass )
=A0enumerable.each do |element|
=A0 =A0to_pass =3D yield to_pass , element
=A0end
=A0to_pass
end


Thanks again,
Alex
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,577
Members
45,054
Latest member
LucyCarper

Latest Threads

Top