Я искал очень похожую вещь сегодня, и общее мнение состоит в том, что «невозможно» превратить «ноль» в «ноль», не нарушая все. Итак, вот как вы делаете невозможное:
class ::BasicObject
def null!() self end
def nil!() self end
end
class ::NilClass
NULL_BEGIN = __LINE__
def __mobj__caller()
caller.find do |frame|
(file, line) = frame.split(":")
file != __FILE__ || !(NULL_BEGIN..NULL_END).cover?(line.to_i)
end
end
def null?()
@@null ||= nil
@@null && @@null == __mobj__caller
end
def null!()
@@null = __mobj__caller
self
end
def nil!
@@null = nil
self
end
def method_missing(name, *args, &block)
if null?
self
else
nil!
self
end
end
NULL_END = __LINE__
end
(простите за форматирование, если оно облажалось, я делаю это на iPhone)
Это добавляет "ноль!" метод как ноль, так и всех объектов в целом. Для обычных объектов это не работает, и все работает как обычно (в том числе выдает исключения, когда методы отсутствуют). Для nil, однако, он не будет генерировать исключения, независимо от того, сколько методов в цепочке, и он все равно будет оцениваться точно как nil (так как это все еще обычный nil). Новое поведение ограничено только одной строкой, из которой "null!" назывался. После этого ноль возвращается к нормальному поведению.
if obj.null!.foo.bar.baz
puts "normal execution if this is valid for obj"
end
obj = nil
if obj.null!.foo.bar.baz
puts "this will not print, and no exception"
end
if obj.foo.bar.baz
puts "this will throw an exception as usual"
end
Это достигается путем обхода дерева вызовов и маркировки первого файла / строки, который не является его собственным кодом, и использования его в качестве флага, чтобы указать, что он сейчас находится в «нулевом» режиме. Пока файл / строка не изменятся, он будет продолжать работать как «ноль». Как только он видит, что переводчик продолжает работу, он возвращается к нормальному поведению.
Предостережения для этого подхода:
Ваше заявление должно быть в одной строке.
Ваше утверждение, вероятно, должно содержать фактический номер файла и строки (я не проверял его в IRB, но я подозреваю, что это не сработает). Без этого он не может сказать, что он все еще выполняется на та же строка, поэтому она, вероятно, сразу вернется к нормальной жизни.
Новое поведение будет продолжать влиять на другие ноли до конца строки. Вы можете заставить его действовать как обычно снова, позвонив "ноль!" позже в линии, хотя. Например:
obj.null! .Foo.bar.nil! || normal.nil.behavior
Все, что успешных вызовов метода делает в своих собственных телах, которые вызывают несуществующие методы против nil или вызывают "null!" вероятно, сбросит поведение, поскольку они имеют место при других номерах строк, но, теоретически, это не должно быть проблемой, если вы не делаете очень странные вещи.
Если вам нравятся такие забавные вещи, у меня есть гем под названием "mobj" на gemcutter с кучей этих дурацких хакерских программ или вытащите источник отсюда:
https://github.com/gnovos/mobj
У меня есть другой драгоценный камень, который мог бы сделать это по-другому, если вы хотите более надежное решение (и не возражаете обернуть вещи в блоки):
require "ctx"
class NilClass
ctx :null do
def missing_method(*) self end
end
end
А потом, где бы вы ни хотели, это поведение;
obj = nil
...
ctx :null do
if obj.foo.bar
puts "inside ctx block, acts like 'null!' from above, but extends to everything up and down the stack that inside of the ctx scope"
end
end
if obj.foo.bar
puts "outside ctx block, should be back to normal again"
end
Я не проверял этот последний, но он, вероятно, будет работать. Если вас это интересует, вы можете найти ctx здесь:
https://github.com/gnovos/ctx
Ура!