[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Design problem with 'inject'

Gary Boone

11/15/2006 6:47:00 AM


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.


--
Posted via http://www.ruby-....

20 Answers

Marcel Molina Jr.

11/15/2006 6:51:00 AM

0

On Wed, Nov 15, 2006 at 03:46:44PM +0900, 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

You need to do this:

next sum if i == 3

Also:

sum += 1

This unecessarily modifies the sum.

sum + 1

Will suffice.

marcel
--
Marcel Molina Jr. <marcel@vernix.org>

Urabe Shyouhei

11/15/2006 6:55:00 AM

0

> Problem:
>
> arr.inject(0) do |sum, i|
> next if (i==3)

next sum if (i == 3)

> sum += i
> done



Farrel Lifson

11/15/2006 6:55:00 AM

0

On 15/11/06, Gary Boone <dr@garyboone.com> 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.
>
>
> --
> Posted via http://www.ruby-....
>
>

I agree using next with inject can be dangerous and probably should be
avoided. You can however get round it by just returning the original
value of sum when the condition is met:
arr.inject do |sum,i|
(sum==3) ? sum : sum + i
end

Farrel

Farrel Lifson

11/15/2006 7:00:00 AM

0

On 15/11/06, Marcel Molina Jr. <marcel@vernix.org> wrote:
> On Wed, Nov 15, 2006 at 03:46:44PM +0900, 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
>
> You need to do this:
>
> next sum if i == 3
>
> Also:
>
> sum += 1
>
> This unecessarily modifies the sum.
>
> sum + 1
>
> Will suffice.
>
> marcel
> --
> Marcel Molina Jr. <marcel@vernix.org>

Whoa! That is good to know.

Farrel

Luther

11/15/2006 7:14:00 AM

0

--- Gary Boone <dr@garyboone.com> 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

You should use a '+' instead of a '+='. The return value of the block
will be assigned to the next iteration's value for 'sum', so it
doesn't make sense to modify 'sum' inside the block.

> 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

Try it like this:

arr.inject(0) do |sum, i|
if i == 3
sum
else
sum + i
end
end

Any time you want to skip an element, simply return 'sum'. This will
carry the value from the last iteration on over to the next
iteration, doing nothing with the current element. As far as I can
see, you should never have to use 'next' in an inject block, unless
you really want the intermediate result to be nil.

Luther Thompson



____________________________________________________________________________________
Sponsored Link

$420k for $1,399/mo.
Think You Pay Too Much For Your Mortgage?
Find Out! www.LowerMyBills.com/lre

Pit Capitain

11/15/2006 8:38:00 AM

0

Gary Boone schrieb:
> 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.

Gary, that's not true. The version of inject you use (with an explicit
initial value for the accumulator) works as follows:

def my_inject(initial_value)
accumulator = initial_value
self.each do |element|
accumulator = yield(accumulator, element)
end
accumulator
end

The result of the block simply becomes the next value of the accumulator.

Regards,
Pit

Robert Klemme

11/15/2006 8:42:00 AM

0

On 15.11.2006 07:50, Marcel Molina Jr. wrote:
> On Wed, Nov 15, 2006 at 03:46:44PM +0900, 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
>
> You need to do this:
>
> next sum if i == 3
>
> Also:
>
> sum += 1
>
> This unecessarily modifies the sum.
>
> sum + 1
>
> Will suffice.
>
> marcel

In this case I would not even resort to #next. This seems much more
straightforward:

arr.inject(0) {|sum, i| i == 3 ? sum : sum + i}

or, if you do not like the ternary operator

arr.inject(0) {|sum, i| if i == 3 then sum else sum + i end}

IMHO #next is best used if the block is /long/ and you want to short
circuit to the next iteration. If it is a short block like in this case
#next does not bring any benefits and in fact makes it more complicated
to understand - at least in my opinion.

Kind regards

robert

Gary Boone

11/15/2006 8:51:00 AM

0


Those are several good suggestions.

next sum if (i == 3)

strikes me as simple and clear. Provided you know that the block result
is what matters, you can stay out of trouble.

Simplifying the example, the trouble with inject is that

arr.inject(0) { |sum, i| sum += i }
arr.inject(0) { |sum, i| sum + i }

both produce the same result. Yuk.

Maybe it should be posted somewhere as a potential 'gotcha'.

--
Posted via http://www.ruby-....

Matthew Smillie

11/15/2006 10:10:00 AM

0

On Nov 15, 2006, at 8:51, Gary Boone wrote:

>
> Those are several good suggestions.
>
> next sum if (i == 3)
>
> strikes me as simple and clear. Provided you know that the block
> result
> is what matters, you can stay out of trouble.
>
> Simplifying the example, the trouble with inject is that
>
> arr.inject(0) { |sum, i| sum += i }
> arr.inject(0) { |sum, i| sum + i }
>
> both produce the same result. Yuk.
>
> Maybe it should be posted somewhere as a potential 'gotcha'.

I think it could be that I'm just too familiar with the whole
accumulator thing, but I'm having trouble even imagining a case where
you'd want it any other way. Why would you want those to produce
different results, and - here I can't even guess - what would those
different results actually be?

I have a hunch that the key phrase is "Provided you know that the
block result is what matters, you can stay out of trouble." I mean,
that's sort of inherent in the construct. If you don't know that,
I'd you're going to have a lot of trouble with #inject.

m.s.

dblack

11/15/2006 1:08:00 PM

0