Просто для удовольствия - могу ли я написать собственный NilClass для конкретного использования?
РЕДАКТИРОВАТЬ | Или другой вопрос, на эту же тему предмета. Могу ли я написать свое собственное определение класса, которое заставит работать все следующее?
o = WeirdObject.new
puts "Object o evaluates as true in boolean expressions" if o # Line never output
puts "Object o is nil?" if o.nil? # Line is output
Я могу сделать nil?
все просто, так как это всего лишь метод. Но не знаю, как сделать так, чтобы оно оценивалось как неверное значение в логическом выражении (включая только самое основное выражение "o").
Оригинальный вопрос следует...
Еще больше из любопытства о том, сколько удовольствия я могу получить с Ruby (1.9.2) на самом деле...
Когда пользователь не вошел в систему, я хотел бы иметь возможность обнаружить его с unless @user
или если @user.nil?
и т.д., но в то же время я хотел бы иметь возможность вызывать методы User
на этом объекте и пусть они возвращают то же самое NilClass
(ala Objective-C), так как в большинстве мест это устранит необходимость в шаблоне if @user
код, пока еще действует как nil
в логических выражениях.
Я вижу, что вы не можете подкласс NilClass
, так как у него нет #new
метод. Вы можете добавлять методы непосредственно к экземпляру, но это одноэлементное приложение, поэтому это могло бы вызвать проблемы, если бы единственное место, где я хотел наблюдать это поведение, было с незарегистрированными пользователями.
Является ли это возможным?
Я имею в виду, что-то вроде этого:
class CallableNil < NilClass
def method_missing(meth, *args, &block)
self
end
end
user = CallableNil.new # or .singleton, or something
puts "Got here" unless user # Outputs
puts "And here" unless user.nil? # also outputs
puts user.username # no-op, returns nil
Это в основном то, как Objective-C обрабатывает ноль, и это полезно в некоторых обстоятельствах.
Я думаю, я не мог бы создать подкласс NilClass и просто реализовать методы, которые заставили бы его быть ложным в логических выражениях?
РЕДАКТИРОВАТЬ | Ха-ха, ты действительно сильно разбиваешь Ruby, если переопределяешь верхний уровень NilClass
... он вообще перестает отвечать на вызов любого метода.
>> class NilClass
>> def method_missing(meth, *args, &block)
>> self
>> end
>> end
>>
?> nil.foo
>>
?>
?> puts "here"
>> quit
>> # WTF? Close dammit!
4 ответа
Если вашим основным требованием является возможность вызова методов на nil
значение без вызова исключения (или проверки того, что оно определено), ActiveSupport
добавляет Object#try
что дает вам возможность сделать это:
instance = Class.method_that_returns_nil
unless instance
puts "Yes, we have no instance."
end
instance.try(:arbitrary_method) # => nil
instance.try(:arbitrary_method, "argument") # => nil
Как вы уже видели NilClass
имеет некоторые интересные побочные эффекты.
Другое потенциальное решение (не поточно-ориентированное):
def null!
@@null_line = caller.first
end
class NilClass
def method_missing(name, *args, &block)
if caller.first == @@null_line
nil
else
super
end
end
end
null!; nil.a.b.c.d.e # returns nil
nil.a # throws an error
Как правило, не рекомендуется менять nil
объект таким образом - безотказный. Я бы порекомендовал использовать egonil или гем andand. Чтобы узнать больше о ссылках на эту тему [и для удовольствия;)], прочитайте этот пост в блоге.
Я искал очень похожую вещь сегодня, и общее мнение состоит в том, что "невозможно" превратить "ноль" в "ноль", не ломая все. Итак, вот как вы делаете невозможное:
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:
Ура!