Bang != Simple (Maybe)

Something that’s tripped me up while learning Ruby is the use of exclamation points at the end of method names. Based on stack overflow searches and experimentation, I (erroneously) assumed that bang means that a method changes its receiver in place. This is only sometimes true.

The single most important thing to realize about bang methods in Ruby is that it is a naming convention. The “!” itself is not doing anything. Nothing!


This is what happens when bang makes art.

Danger! More stock images ahead!
Seriously!

When used in conditional statements, “!” means “not,” for example:

001 > !true
=> false
002 > !nil
=> true
003 > funny = “this”
004 > !funny
=> false

That’s cool. But also not the same “!” that we see (and use!) in our method names.


Bang Methods

Let’s check out some irb code to see why bang methods can be so great:

001 > example_array = [1,4,3,5,7,8,2,6]
=> [1, 4, 3, 5, 7, 8, 2, 6]
002 > example_array.sort
=> [1, 2, 3, 4, 5, 6, 7, 8]
003 > example_array
=> [1, 4, 3, 5, 7, 8, 2, 6]
004 > example_array.sort!
=> [1, 2, 3, 4, 5, 6, 7, 8]
005 > example_array
=> [1, 2, 3, 4, 5, 6, 7, 8]

In the above example, sort! sorts our example_array in place, which can be pretty nifty. If we use the sort! method in a program, we can save ourselves the hassle (and memory) of declaring a new variable to hold our sorted array, we just sort the original in place.

So why not use bangs everywhere?

The simple answer is because bang doesn’t mean the same thing in every method. In fact, the bang is just a Ruby convention which alerts Rubyists to the fact that the method they are working with is “dangerous.” Often that means that the method acts on the receiver (and is destructive) but that is not always the case(see #exit!). Additionally, not all destructive methods have bangs (think #pop and #shift for example)

I started making the above discoveries the hard way. For example, below I am trying to make a simple method to filter all the duplicates out of an array:

def unique_filter(array)
array.uniq!
end
unique_filter([1,1,2,3,4,4,4,4,5])
=> [1, 2, 3, 4, 5]
unique_filter([1,2,3,4,5,6,7,8])
=> nil

Lame. I wanted to filtered out the repeats; I definitely don’t want to get back nil if there are none to remove, I want the original array. If we check out the documentation for #uniq!, this makes sense, the method returns nil if it doesn’t do anything. Perhaps the “!” should’ve tipped us off to be careful with our returns andthe effects on the receiver? Next time! For now, here’s a little work around:

def unique_filter(array)
array.uniq!
array
end
unique_filter([1,1,2,3,4,4,4,4,5])
=> [1, 2, 3, 4, 5]
unique_filter([1,2,3,4,5,6,7,8])
=> [1,2,3,4,5,6,7,8]

Much better! Ok, maybe not perfect, but it works. The important thing to notice is that bang — rather than telling us anything about the method — acts as a flag, alerting the programmer that this method is distinct from it’s ‘safe’ counterpart. If we want to learn what to expect from the method, the documentation is a good place to start.

Banging It All Together

Hopefully the above is a helpful primer on why bangs can be useful and why we should be careful with them. Since the bang is a naming convention, the only way it will continue to make sense is if we all play nice and follow the rules. For that, David A. Black:*

“The bang has no significance to Ruby internally… but, by convention, bang labels a method ‘dangerous’ — The Well-Grounded Rubyist, pp 197

Here are some more useful guidelines from The Well Grounded Rubyist:

  • “Destructiveness and Danger Vary Independently” — put another way, it is not the destructiveness that makes bangs dangerous, it is the fact that they behave differently from their un-banged counterparts…
  • “Don’t Use ! Except in m/m! Method Pairs” — this is a big one, when writing your own methods, the bang means you’ve created a dangerous version of a safe method, not simply a method that modifies in place or destructively
  • “Don’t Equate ! Notation with Destructive Behavior, or Vice Versa”

Sources and Thanks

All the smart stuff here from The Well Grounded Rubyist and the following blog post:

http://dablog.rubypal.com/2007/8/15/bang-methods-or-danger-will-rubyist