1

IRB appears to have strange behaviour when defining a method called !.

To reproduce this, enter the following into IRB:

def !
  puts "foo"
end

Upon creating the method, IRB infinitely prints foo:

irb(main):001:0> def !
irb(main):002:1>   puts "foo"
irb(main):003:1> end
foo
foo
foo
...

As far as I know, you can't directly call a method named ! from Ruby syntax; you have to use send instead. Edit: You can invoke ! as a prefix operator; it's just negation: !x

Why does this definition cause IRB to loop infinitely? Does IRB rely on a method named ! for printing its prompt or something similar?

I'm using Ruby 2.4.3 and IRB 0.9.6 on Windows 10.

Aaron Christiansen
  • 11,584
  • 5
  • 52
  • 78

1 Answers1

2

tl;dr: Overriding ! outside of a class is a very weird thing to do! There are countless ways that you can "break" ruby by doing crazy things like this - so you may find it fun to play around with such strange ideas, but obviously don't do this in important code!

In ruby, all classes inherit from the top-level base class: BasicObject. This class defines top-level object negation - i.e. Whenever you write

!foo

this is actually calling a method called ! on your object foo:

foo.send(:!)

This makes it possible (although it's a very rare thing to do!) to redefine the method on a specific class. For example, when implementing the null object pattern you could do something like this:

class NullObject
  def !
    true
  end
end

my_null = NullObject.new
!!my_null #=> false

(Normally, the only objects that would return false in the above line are nil and false!)

Now then, back to your example. What you actually did here was define a method called ! on the class Object (and didn't call super to trigger the original method!). In other words, you basically re-defined the response a fundamental method that gets used all over the place internally. Something, somewhere (??) got confused by this bizarre behaviour and failed non-gracefully.

irb(main):001:0> def !
irb(main):002:1> puts "foo"
irb(main):003:1> super # <-- !! This stops it from breaking completely !!
irb(main):004:1> end
=> :!
irb(main):005:0* method(:!)
foo
foo
=> #<Method: Object#!>
irb(main):006:0> method(:!).source_location
foo
foo
=> ["(irb)", 1]
irb(main):007:0> method(:!).super_method
foo
foo
=> #<Method: BasicObject#!>

Here are some other ways you could re-define methods to cause bizarre behaviour/errors, for example:

def nil?
  true
end
# Will now die in weird ways!

class String
  def ===(other)
    true
  end
end

"ruby" === "awesome"
#=> true
Tom Lord
  • 27,404
  • 4
  • 50
  • 77