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 Edit: You can invoke !
from Ruby syntax; you have to use send
instead.!
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.
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