From: Robert Klemme Date: 2006-11-16T01:00:08+09:00 Subject: Re: Design problem with 'inject' On 15.11.2006 16:09, ara.t.howard@noaa.gov wrote: > On Wed, 15 Nov 2006, Gary Boone wrote: > >> Ruby's inject has a design that can lead to hard to find bugs. The >> problem is that you don't have to specify what is summed; the value of >> the block is summed. That means 'next' can lead to a bug. Here's an >> example: >> >> No problem: >> >> arr.inject(0) do |sum, i| >> sum += i >> done >> >> But suppose you need to skip some elements, so you add a 'next' >> statement. >> Problem: >> >> arr.inject(0) do |sum, i| >> next if (i==3) >> sum += i >> done >> >> This breaks. If i is 3, then when the next occurs, the value of the >> block is nil. The value of the block is added to sum, but because "+" >> isn't defined for nil, there's an exception. Note that this isn't due to >> the line "sum += i"; it's due to the design of inject: the value of the >> block is added to sum. >> >> The real problem is that 'inject' has two semantics: 1) it adds onto sum >> using an explicit "sum +=" or 2) is adds the value of the block. A >> better approach would be allow only one way to accumulate. That way, you >> can't make a change that inexplicitly changes the function from one >> semantics to another. > > this last part is dead wrong, the semantics of inject have __nothing__ > to do > with summing, or even accumulating __anything__. the semantics of > inject is > merely that it iterates an enumerable, on the first iteration it passes > > |arg, enumerable_element| > > to the block, and on subsequent passes > > |value_of_previous_block_call, enumerable_element| > > is passedy Small correction: if called *without arguments* the first invocation will look like this: |enumerable_element_1, enumerable_element_2| >> %w{foo bar baz}.inject {|*a| p a} ["foo", "bar"] [nil, "baz"] => nil >> %w{foo bar baz}.inject(nil) {|*a| p a} [nil, "foo"] [nil, "bar"] [nil, "baz"] => nil This is actually useful to do something like this: >> %w{foo bar baz}.inject() {|a,b| a+", "+b} => "foo, bar, baz" (Efficiency is another story.) Either variant is useful for summing and it depends on whether you need the information that a container was empty or not: >> [1,2,3].inject {|s,x| s+x} => 6 >> [].inject {|s,x| s+x} => nil >> [1,2,3].inject(0) {|s,x| s+x} => 6 >> [].inject(0) {|s,x| s+x} => 0 > so, you see, inject itself does in fact do exactly one thing. if you > happen to do more in the block it's no fault of inject. Absolutely. Regards robert