Почему определение метода с именем ! сломать ИРБ?

IRB ведет себя странно при определении метода с именем !.

Чтобы воспроизвести это, введите в IRB следующее:

def !
  puts "foo"
end

После создания метода IRB бесконечно печатает foo:

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

Насколько я знаю, вы не можете напрямую вызывать метод с именем ! из синтаксиса Ruby; вместо этого вы должны использовать send. Редактировать: вы можете вызывать ! в качестве префиксного оператора; это просто отрицание: !x

Почему это определение приводит к бесконечному циклу IRB? Полагается ли IRB на метод с именем ! для вывода подсказки или что-то подобное?

Я использую Ruby 2.4.3 и IRB 0.9.6 в Windows 10.


person Aaron Christiansen    schedule 28.03.2018    source источник
comment
Вы переопределяете BasicObject#!   -  person Stefan    schedule 28.03.2018
comment
@Stefan Я понятия не имел, что можно даже отменить отрицание. Спасибо!   -  person Aaron Christiansen    schedule 28.03.2018
comment
Рубин хорош тем, что если попросить его медленно сгореть, он улыбнется и обольется бензином.   -  person Silvio Mayolo    schedule 28.03.2018
comment
К вашему сведению — просто введите method(:!) в IRB, чтобы увидеть происхождение метода.   -  person Stefan    schedule 28.03.2018
comment
@SilvioMayolo дает вам достаточно веревки, чтобы выстрелить себе в ногу :)   -  person engineersmnky    schedule 28.03.2018


Ответы (1)


Вкратце: переопределение ! вне класса — это очень странная вещь! Существует бесчисленное множество способов «сломать» ruby, делая подобные сумасшедшие вещи, так что вам может показаться забавным экспериментировать с такими странными идеями, но, очевидно, не делайте этого в важном коде!

В ruby ​​все классы наследуются от базового класса верхнего уровня: BasicObject. Этот класс определяет отрицание объекта верхнего уровня, т.е. всякий раз, когда вы записывать

!foo

на самом деле это вызов метода с именем ! для вашего объекта foo:

foo.send(:!)

Это позволяет (хотя это делается очень редко!) переопределить метод для конкретного класса. Например, при реализации шаблона пустой объект вы можете сделать что-то вроде этого:

class NullObject
  def !
    true
  end
end

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

(Обычно единственными объектами, которые возвращают false в приведенной выше строке, являются nil и false!)

А теперь вернемся к вашему примеру. На самом деле здесь вы определили метод с именем ! в классе Object (и не вызывали super для запуска исходного метода!). Другими словами, вы фактически переопределили ответ как фундаментальный метод, который используется повсеместно внутри. Что-то, где-то (??) запуталось в этом странном поведении и не изящно потерпело неудачу.

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#!>

Вот несколько других способов, которыми вы могли бы переопределить методы, чтобы вызвать странное поведение/ошибки, например:

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

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

"ruby" === "awesome"
#=> true
person Tom Lord    schedule 28.03.2018