3

编辑 | 或者另一个问题,关于同一个客体主题。我可以编写自己的类定义来使以下所有操作都起作用吗?

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布尔表达式一样工作。

我看到你不能 subclass 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 处理 nil 的方式,它在某些情况下很有用。

我想我不能继承 NilClass 而只是实现会导致它在布尔表达式中为假的方法?

编辑 | 哈哈,如果你重新定义顶层,你真的会严重破坏 Ruby NilClass......它根本停止响应任何方法调用。

>> class NilClass
>>   def method_missing(meth, *args, &block)
>>     self
>>   end
>> end
>> 
?> nil.foo
>> 
?> 
?> puts "here"
>> quit
>> # WTF? Close dammit!
4

4 回答 4

3

如果您的主要要求是能够在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有一些有趣的副作用。

于 2011-05-08T02:48:21.980 回答
2

一般来说,不建议以nil这种方式更改对象 - fail-fast。我建议使用egonilandand gem。有关此主题的更多阅读/链接 [和乐趣 ;)],请查看此博客文章

于 2011-05-08T02:41:45.943 回答
2

另一个潜在的解决方案(不是线程安全的):

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
于 2012-11-21T08:13:03.520 回答
0

我今天正在寻找一个非常相似的东西,普遍的共识是,在不破坏一切的情况下将“nil”变成“null”是“不可能的”。所以,这就是你不可能做到的:

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(因为它仍然只是一个普通的 nil)。新行为的范围仅限于“null!”所在的单行。被称为。之后 nil 返回它的正常行为。

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

它通过遍历调用树并标记不是它自己的代码的第一个文件/行来实现这一点,并将其用作标志来指示它现在处于“null”模式。只要文件/行不改变,它将继续像“null”一样。一旦它看到解释器继续前进,它就会将自己重置为正常行为。

这种方法的注意事项是:

  • 您的陈述必须全部放在一行中。

  • 你的语句可能必须有一个实际的文件和行号(我没有在 IRB 中测试过,但我怀疑它不会工作)。没有这些,它无法判断它仍在同一行上执行,所以它会可能马上恢复正常。

  • 新行为将继续影响其他 nil,直到行尾。您可以通过调用“nil!”来强制它再次正常运行。不过,在后面。例如:

    obj.null!.foo.bar.nil!|| normal.nil.behavior

  • 任何成功的方法调用在他们自己的身体中所做的任何事情都会针对 nil 调用不存在的方法或调用“null!”。可能会重置行为,因为这些行为发生在其他行号,但是,理论上,这不应该是一个问题,除非你正在做非常奇怪的事情。

如果你喜欢这种“有趣”的东西,我在 gemcutter 上有一个名为“mobj”的 gem,里面有一堆古怪的骇客,或者从这里获取源代码:

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

干杯!

于 2012-11-21T05:42:04.570 回答