From: Rick DeNatale Date: 2007-11-12T08:15:48+09:00 Subject: Re: equivalent injecting implementations? On Nov 11, 2007 1:39 PM, Trans wrote: > > > On Nov 11, 9:50 am, "Rick DeNatale" wrote: > > Yes, I suspected that this was what Trans was trying to do, but since > > the second proposed 'maybe equivalent' implementation didn't seem to > > be doing the same thing, I was probing for what he really wanted. > > > > Having said that, and played a bit with the concept. I'm not sure how > > useful an injecting method (at least with that name is). > > > > As Robert Klemme points out, it breaks depending on what you are > > injecting. And it's subject to "cargo culting." > > > > module Enumerable > > def injecting(obj, &blk) > > inject(obj) { |mem, var| yield(mem, var); mem } > > end > > end > > > > As Ezra points out: > > > > [1,2,3].inject({}) { |memo,n| memo[n] = n ; memo} # => {1=>1, 2=>2, 3=>3} > > [1,2,3].injecting({}) { |memo,n| memo[n] = n} # => {1=>1, 2=>2, 3=>3} > > > > But this only works because the block has a side-effect on the memo > > object. And yes, in cases like this each instead of inject will have > > the same side effects. > > > > [1,2,3].inject(0) { |memo,n| memo + n} # => 6 > > [1,2,3].injecting(0) { |memo,n| memo + n} # => 0 > > > > Here's where the cargo cult danger comes in. Someone who comes across > > 'injecting' without understanding it might run into things like this. > > Hmmm... does this mean that the same effect can be achieved with: > > module Enumerable > def inject!(obj) > each { |var| yield(obj, var) } > obj > end > end If you mean the same effect as the current inject. NO. This fails for the case I cited: [1,2,3].inject(0) { |memo,n| memo + n} # => 6 which with the definition above would return 0. This is an example of the 'normal' use of inject: qri Enumerable#inject ------------------------------------------------------ Enumerable#inject enum.inject(initial) {| memo, obj | block } => obj enum.inject {| memo, obj | block } => obj ------------------------------------------------------------------------ Combines the elements of enum by applying the block to an accumulator value (memo) and each element in turn. At each step, memo is set to the value returned by the block. The first form lets you supply an initial value for memo. The second form uses the first element of the collection as a the initial value (and skips that element while iterating). # Sum some numbers (5..10).inject {|sum, n| sum + n } #=> 45 # Multiply some numbers (5..10).inject(1) {|product, n| product * n } #=> 151200 # find the longest word longest = %w{ cat sheep bear }.inject do |memo,word| memo.length > word.length ? memo : word end longest #=> "sheep" # find the length of the longest word longest = %w{ cat sheep bear }.inject(0) do |memo,word| memo >= word.length ? memo : word.length end longest #=> 5 The redefinition would change the results the last example to return 0 instead of 5. And it would make the other ones illegal since it requires the initial parameter. Now I noticed that you picked up David Black's suggestion of inject! instead of inject, but I took the :-) that he added as an indication that it was tongue in cheek. The main point here is that Enumerable#inject as it's defined does not rely on side effects in the block to change the memo object. > I think the common usecase is simply not having to create the initial > object outside the loop statement, eg. > > obj = 0 > each { |i| ... obj ... } > If I may be so bold as to suggest that injecting as posited by Ezra might really be a result of cargo culting the "k combinator" represented by Object#returning with a slightly less than perfect understanding of Enumerable#inject. His motivating example: [1,2,3].inject({}) { |memo,n| memo[n] = n ; memo} # => {1=>1, 2=>2, 3=>3} Is an unusual use of inject. It might just as well be written as: h = {}; [1, 2, 3].each {|i| h[i] = i}; h Which admittedly is uglier due to the need to use the temporary h, and rename it. But here's an alternative way to accomplish Ezra's use case using Object#returning (which is implemented as part of the ActiveSupport gem): # This is the definition of Object#returning from ActiveSupport class Object def returning(value) yield(value) value end end # Ezra's example of injecting was: # [1,2,3].injecting({}) { |memo,n| memo[n] = n } # => {1=>1, 2=>2, 3=>3} # The same thing using returning returning({}) {|hsh| [1,2,3].each {|i| hsh[i] = i}} # => {1=>1, 2=>2, 3=>3} -- Rick DeNatale My blog on Ruby http://talklikeaduck.denhaven2.com/