只是为了好玩 - 我可以为特定用途编写自定义 NilClass 吗?

发布于 2024-11-06 01:43:03 字数 1547 浏览 0 评论 0原文

编辑 |或者关于同一对象主题的另一个问题。我可以编写自己的类定义来使以下所有内容正常工作吗?

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 或 except @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 处理 nil 的方式,并且在某些情况下很有用。

我想我不能子类 NilClass 并只实现会导致它在布尔表达式中为 false 的方法?

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

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

EDIT | Or another question, on the same object subject. Can I write my own class definition that would cause the following all to work?

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

I can do the nil? thing easy, since that's just a method. But no idea how to make it evaluate as a non-true value in a boolean expression (including just the most basic expression "o").

Original question follows...

More out of curiosity as to how much fun I can have with Ruby (1.9.2) really...

When a user is not logged in, I'd like to be able to detect it with unless @user or unless @user.nil? etc, but at the same time, I'd like to be able to call the methods of User on this object and have them return the same NilClass (ala Objective-C), since in most places this would remove the need for boilerplate if @user code, while still acting like nil in boolean expressions.

I see that you can't subclass NilClass, since it doesn't have a #new method. You can add methods directly to the instance, but it's a singleton, so this would cause issues if the only place I wanted to observe this behaviour was with non-logged-in users.

Is it possible?

What I mean, is something like this:

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

This is basically how Objective-C handles nil and it's useful in some circumstances.

I guess I could not subclass NilClass and just implement the methods that would cause it to be false in boolean expressions?

EDIT | Haha, you really break Ruby pretty badly if you redefine the top-level NilClass... it stops responding to any method call at all.

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

零度° 2024-11-13 01:43:03

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

If your primary requirement is to be able to call methods on a nil value without raising an exception (or having to check that it's defined), ActiveSupport adds Object#try which gives you the ability to do this:

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

As you've seen, screwing around with NilClass has some interesting side-effects.

不甘平庸 2024-11-13 01:43:03

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

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

Another potential solution (not thread safe):

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
稳稳的幸福 2024-11-13 01:43:03

一般来说,不建议以这种方式更改 nil 对象 -- 快速失败。我建议使用 egonilandand 宝石。有关此主题的更多阅读/链接 [并且为了好玩;)],请查看 这篇博文

Generally, it's not recommended to change the nil object in this way -- fail-fast. I'd recommend to use the egonil or the andand gem. For some more reading/links on this topic [and for fun ;)], checkout this blog post .

郁金香雨 2024-11-13 01:43:03

我今天正在寻找一个非常相似的东西,普遍的共识是,在不破坏一切的情况下将“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 上做的)

这会添加一个“null!”一般情况下,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”。一旦它发现解释器已经移动,它就会将自己重置回正常行为。

此方法的注意事项是:

  • 您的语句必须全部放在一行中。

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

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

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

  • 成功的方法调用在其自己的主体中执行的任何操作,这些操作针对 nil 调用不存在的方法或调用“null!”可能会重置行为,因为这些行为发生在其他行号,但是从理论上讲,除非您正在做非常奇怪的事情,否则这不应该成为问题。

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

https://github.com/gnovos/mobj

我有另一个 gem,可以以不同的方式做到这一点,如果你想要一个更强大的解决方案(并且不'不介意将东西包装成块):

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

干杯!

I was looking for a very similar thing today and the general consensus is that it's "impossible" to turn "nil" into "null" without breaking everything. So, this is how you do impossible:

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

(Forgive the formatting if it's screwed up, I'm doing this on an iPhone)

This adds a "null!" method to both nil and all objects in general. For normal objects, it's a no-op, and things work as normal (including throwing exceptions when methods are missing). For nil, however, it won't throw exceptions, no matter how many methods are in the chain, and it will still evaluate exactly as nil (since it's still just a normal nil). The new behavior is scoped only to the single line from which "null!" was called. Afterwards nil returns to it's normal behavior.

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

It achieves this by walking up the call tree and marking the first file/line that isn't it's own code, and using that as a flag to indicate that it is now in "null" mode. As long as the file/line don't change, it will continue to act like a "null". As soon as it sees that the interpreter has moved on, it resets itself back to normal behavior.

The caveats for this approach are:

  • Your statement must all fit on a single line.

  • Your statement probably must have an actual file and line number (I've not tested it in IRB, but I suspect it will not work).. Without these it can't tell that it's still executing on the same line, so it will probably revert to normal right away.

  • The new behavior will continue to affect other nils until the end of the line. You can force it to act like normal again by calling "nil!" later in the line, though. For example:

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

  • Anything that successful method calls do in their own bodies that call non existant methods against nil or call "null!" will probably reset the behavior since those take place at other line numbers, but, in theory, that shouldn't be a problem unless you are doing very weird things.

If you'd like this kind of "fun" stuff, I have a gem called "mobj" on gemcutter with a bunch of this wacky hackery, or pull the source from here:

https://github.com/gnovos/mobj

I have another gem that could do this in a different way, if you want a more robust solution (and don't mind wrapping things in blocks):

require "ctx"

class NilClass
    ctx :null do
        def missing_method(*) self end
    end
end

And then, anywhere that you want this behavior;

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

I haven't tested this last one, but it will probably work. If this interests you, you can find ctx here:

https://github.com/gnovos/ctx

Cheers!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文