Unexpected behavior in inject

Discussion in 'Ruby' started by Alex Fort, Jun 15, 2010.

  1. Alex Fort

    Alex Fort Guest

    [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
    Alex Fort, Jun 15, 2010
    #1
    1. Advertising

  2. [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
    Jonathan Nielsen, Jun 15, 2010
    #2
    1. Advertising

  3. Alex Fort

    Josh Cheek Guest

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

    On Tue, Jun 15, 2010 at 9:21 AM, Alex Fort <> wrote:

    > 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
    Josh Cheek, Jun 15, 2010
    #3
  4. Alex Fort

    Alex Fort Guest

    On Tue, Jun 15, 2010 at 10:31 AM, Jonathan Nielsen <> wrot=
    e:
    >> [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
    Alex Fort, Jun 15, 2010
    #4
  5. Alex Fort

    Alex Fort Guest

    On Tue, Jun 15, 2010 at 10:35 AM, Josh Cheek <> wrote:
    > On Tue, Jun 15, 2010 at 9:21 AM, Alex Fort <> wrote:
    >
    >> 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
    Alex Fort, Jun 15, 2010
    #5
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. G Dean Blake

    Unexpected datagrid behavior

    G Dean Blake, Jan 13, 2005, in forum: ASP .Net
    Replies:
    0
    Views:
    310
    G Dean Blake
    Jan 13, 2005
  2. Chuck Bowling

    Unexpected page designer behavior

    Chuck Bowling, Jul 4, 2005, in forum: ASP .Net
    Replies:
    1
    Views:
    434
    Chuck Bowling
    Jul 4, 2005
  3. Victor Bazarov
    Replies:
    0
    Views:
    837
    Victor Bazarov
    Jun 25, 2003
  4. Matthew Moss
    Replies:
    2
    Views:
    100
    Matthew Moss
    Mar 13, 2006
  5. Peña, Botp

    inject does not inject last value

    Peña, Botp, Aug 7, 2006, in forum: Ruby
    Replies:
    4
    Views:
    171
    Peña, Botp
    Aug 7, 2006
Loading...

Share This Page