Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why would #each_with_object and #inject switch the order of block parameters?

#each_with_object and #inject can both be used to build a hash.

For example:

matrix = [['foo', 'bar'], ['cat', 'dog']]

some_hash = matrix.inject({}) do |memo, arr|
  memo[arr[0]] = arr
  memo # no implicit conversion of String into Integer (TypeError) if commented out
end
p some_hash # {"foo"=>["foo", "bar"], "cat"=>["cat", "dog"]}

another_hash = matrix.each_with_object({}) do |arr, memo|
  memo[arr[0]] = arr
end
p another_hash # {"foo"=>["foo", "bar"], "cat"=>["cat", "dog"]}

One of the key differences between the two is #each_with_object keeps track of memo through the entire iteration while #inject sets memo equal to the value returned by the block on each iteration.

Another difference is the order or the block parameters.

Is there some intention being communicated here? It doesn't make sense to reverse the block parameters of two similar methods.

like image 964
mbigras Avatar asked Jan 21 '17 18:01

mbigras


People also ask

Why did or why would?

'Why would you do that?' implies that the speaker thinks there is not a good reason for having done it. In contrast, 'Why did you do that?' implies that the speaker acknowledges that there might have been a reason for doing it and is just asking which reason was actually the cause.

What would you do meaning?

Author has 3.4K answers and 2.7M answer views Updated 1y. The answer to “What will you do” will mean you are going to do something. The answer to “What would you do” means you are going to supply an answer/opinion that might include you doing something or not.


1 Answers

They have a different ancestry.

  • each_with_object has been added to Ruby 1.9 in 2007
  • inject goes back to Smalltalk in 1980

I guess if the language were designed with both methods from the begin they would likely expect arguments in the same order. But this is not how it happened. inject has been around since the begin of Ruby whereas each_with_object has been added 10 years later only.

inject expects arguments in the same order as Smalltalk's inject:into:

collection inject: 0 into: [ :memo :each | memo + each ]

which does a left fold. You can think of the collection as a long strip of paper that is folded up from the left and the sliding window of the fold function is always the part that has already been folded plus the next element of the remaining paper strip

# (memo = 0 and 1), 2, 3, 4  
# (memo = 1 and 2), 3, 4                 
# (memo = 3 and 3), 4                    
# (memo = 6 and 4)                      

Following the Smalltalk convention made sense back then since all the initial methods in Enumerable are taken from Smalltalk and Matz did not want to confuse people who are familiar with Smalltalk.

Nor could anyone have the foresight to know that would happen in 2007 when each_with_object was introduced to Ruby 1.9 and the order of argument reflects the lexical order of the method name, which is each ... object.

And hence these two methods expect arguments in different orders for historical reasons.

like image 72
akuhn Avatar answered Sep 19 '22 22:09

akuhn