5

Why the following code issues an error ?

['hello','stack','overflow'].inject{|memo,s|memo+s.length}

TypeError: can't convert Fixnum into String
        from (irb):2:in `+'
        from (irb):2:in `block in irb_binding'
        from (irb):2:in `each'
        from (irb):2:in `inject'
        from (irb):2

If the initial value is passed, it works OK:

['hello','stack','overflow'].inject(0){|memo,s|memo+s.length}
=> 18
5
  • 1
    I find the following syntax really nice ['hello','stack','overflow'].map(&:length).inject(&:+) but it's certainly not relevant to your question. Commented Nov 30, 2010 at 11:56
  • 1
    @Jonas, yeah, that's cool. And since the reduction is a summatory we can write: ['hello','stack','overflow'].map(&:length).sum Commented Nov 30, 2010 at 12:58
  • 1
    Yes if you are using api.rubyonrails.org/classes/Enumerable.html you can. As far as I know, there's no sum in standard Ruby. Commented Nov 30, 2010 at 13:44
  • Where could I find documentation on .map(&:length).inject(&:+) syntax ? Commented Dec 1, 2010 at 2:07
  • 1
    @Misha: apidock.com/ruby/Symbol/to_proc explains the &:method_name part of the syntax. Enumerable's syntax will explain the map and inject part. Commented Dec 1, 2010 at 2:41

3 Answers 3

16

You have the answer in apidock :

If you do not explicitly specify an initial value for memo, then uses the first element of collection is used as the initial value of memo.

That is, without an initial value, you're trying to do 'hello' + 'stack'.length

Sign up to request clarification or add additional context in comments.

Comments

6

As the error message already tells you, the problem is that you have a TypeError. Just because Ruby is dynamically and implicitly typed doesn't mean that you don't have to think about types.

The type of Enumerable#inject without an explicit accumulator (this is usually called reduce) is something like

reduce :: [a] → (a → a → a) → a

or in a more Rubyish notation I just made up

Enumerable[A]#inject {|A, A| A } → A

You will notice that all the types are the same. The element type of the Enumerable, the two argument types of the block, the return type of the block and the return type of the overall method.

In your case, the types for the block just don't add up. The block gets passed two Strings and it is supposed to return a String. But you call the + method on the first argument (which is a String) with an argument that is an Integer. But String#+ doesn't take an Integer it only takes a String or more precisely something which can be converted to a String, i.e. something that responds to #to_str. That's why you get a TypeError for String#+.

The type of Enumerable#inject with an explicit accumulator (this is usually called fold) is something like

fold :: [b] → a → (a → b → a) → a

or

Enumerable[B]#inject(A) {|A, B| A } → A

Here you see that the accumulator can have a different type than the element type of the collection. Which is precisely what you need.

These two rules generally get you through all Enumerable#inject-related problems:

  1. the type of the accumulator and the return type of the block must be the same
  2. when not passing an explicit accumulator, the type of the accumulator is the same as the element type

Rule #1 will most often bite you when you do something like

acc[key] = value

in your block, because assignments evaluate to the assigned value, not the receiver of the assignment. You'll have to replace this with

acc.tap { acc[key] = value }

In your particular case, the two solutions have already been mentioned. Either use an explicit accumulator

ary.reduce(0){|acc, e| acc + e.length }

or convert to integers first

ary.map(&:length).reduce(:+)

2 Comments

@Andrew Grimm: Thnaks fro teh speling fxies :-)
Thank you for such a detailed answer!
3

Without the initial value, inject uses the first element in the collection as the initial value.

see ruby-doc.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.