Просто для удовольствия - могу ли я написать собственный NilClass для конкретного использования? - PullRequest
2 голосов
/ 08 мая 2011

РЕДАКТИРОВАТЬ | Или другой вопрос, по тому же объекту субъекту. Могу ли я написать свое собственное определение класса, которое заставит работать все следующие элементы?

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 ]

3 голосов
/ 08 мая 2011

Если вашим основным требованием является возможность вызова методов для значения 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 имеет некоторые интересные побочные эффекты.

2 голосов
/ 21 ноября 2012

Другое потенциальное решение (не безопасное для потоков):

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
2 голосов
/ 08 мая 2011

Обычно не рекомендуется менять объект nil таким образом - fail-fast . Я бы рекомендовал использовать egonil или andand gem. Чтобы узнать больше о ссылках на эту тему [и для удовольствия;)], прочитайте это сообщение в блоге .

0 голосов
/ 21 ноября 2012

Я искал очень похожую вещь сегодня, и общее мнение состоит в том, что «невозможно» превратить «ноль» в «ноль», не нарушая все. Итак, вот как вы делаете невозможное:

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

Ура!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...