views:

75

answers:

2

I was always under impression that bang version of Array methods are only different in the way that they modify object in place.

Turns out, some of them like compact!, reject!, flatten!, uniq! also return nil if no changes were made bang bang bang

Check out this:

ruby-1.8.7-p302 > [1,[2]].flatten!
 => [1, 2]
ruby-1.8.7-p302 > [1,2].flatten!
 => nil
ruby-1.8.7-p302 > [1,[2]].flatten
 => [1, 2]
ruby-1.8.7-p302 > [1,2].flatten
 => [1, 2]

Or this:

ruby-1.8.7-p302 > [1,2,nil].compact!
 => [1, 2]
ruby-1.8.7-p302 > [1,2].compact!
 => nil
ruby-1.8.7-p302 > [1,2,nil].compact
 => [1, 2]
ruby-1.8.7-p302 > [1,2].compact
 => [1, 2]

This costed me about an hour of madness debugging and I must say, so far, makes no sense at all.

On the other hand, if they did it this way, there has got to be a reason. This assumption could be very very far from truth (I know, I am a programmer), but since this behaviour is kept in the latest ruby version, I'd assume there is one after all.

Any ideas what it might be?

+7  A: 

The bang (!) methods do modify the current object in place, but they do return nil if there are no affected elements per the documentation. This is useful if, for whatever reason, you need to do something if you did modify the array in question.

if array.flatten!
  puts "Oh yeah... flattened that array!"
end
Dave Pirotte
That is a good point. But you can't do say `return array.flatten!`, which, to me, would seem useful either
artemave
True. If that is what you want, you'd say `return array.flatten` (no bang) which would return you a flattened copy of the original array.
Dave Pirotte
Which forces me to make a copy where I don't need to. Which is exactly how I got into trouble using banged version in the first place.
artemave
Gotcha. Yeah, you'd need to `return array.flatten! || array` (or split it into two statements) which isn't supremely elegant, IMO, but that is the only alternative I can think of at the moment.
Dave Pirotte
It probably was a design flaw that has not been fixed as it would break code. I guess everbody would agree that returning the object itself is more important that knowing if it changed. The Array class is not even coherent: Array#sort! always return the object, even if no changes were made. Python on other hand has a different policy: in-place operations return nil (None) to emphasize it. Lastly: when you're returning an object after an inplace operation maybe the safest and clearest is a simple: 1) obj.operation! + 2) (return) obj.
tokland
@tokland, you're reading my mind
artemave
+1  A: 

I was always under impression that bang version of Array methods are only different in the way that they modify object in place.

Perhaps the problem here is that this impression is not really a correct one: according to David A. Black, ! does not mean that the method changes its receiver; ! means that this method is the "dangerous" version of an otherwise equivalent method, which has the same name minus the !.

Now danger takes many forms (emphasis mine):

Sometimes you get more than one kind of "danger" even within one bang method. Take String#gsub!. This method changes its receiver:

str = "David" 
str.gsub!(/$/, " Black")
str                        # David Black

It also differs from gsub (non-bang) in that if the string does not change, gsub returns a copy of the unchanged string but gsub! returns nil:

str.gsub(/xyz/, "blah")    # David Black
str.gsub!(/xyz/, "blah")   # nil
str                        # David Black

The ! in gsub! gives you a heads-up: it warns you of danger, and that means that before you use the method, you should find out exactly how it behaves. (A simple "ri String#gsub!" should do it.)

This "heads-up" semantics also applies to the bang methods of Array.

Yaser Sulaiman
I also read this post - it makes no sense. The meaning of 'dangerous' is at best determined as a mark of counter-intuitive behaviour. Well, perhaps, they shouldn't have made it that way in the first place?
artemave
And `before you use the method, you should find out exactly how it behaves` is clearly not the ruby way, where normally the entire ecosystem of conventions naturally leads to the right thing.
artemave