Ruby扩展了NilClass

时间:2014-01-10 16:13:32

标签: ruby boolean null

我正在尝试为应用程序扩展Ruby的NilClass,以便任何方法调用都将返回nil(实际上是self,新的扩展nil)。目的是避免许多额外的零检查逻辑,例如

results = data.nil? nil : data.process()
output = results.nil? nil : results.format()

并将其替换为

output = data.process().format()

如果在链中的任何地方返回nil将返回nil。

让一个类响应任意消息很容易:

class SuperNil
  def method_missing(sym, *args)
    return self
  end
end

现在,supernil = SuperNil.new; supernil.anything返回对象supernil。但是,这并不是真的,因为它会评估为真。是否会以某种方式扩展NilClass,以便我的对象评估为false?我遇到麻烦的地方是,即使在扩展NilClass之后,我也无法创建SuperNil的对象。 NilClass.new给出了一个错误,SuperNil.new也是如此。

是否可以创建NilClass扩展的对象?它会评估为假吗?

4 个答案:

答案 0 :(得分:5)

尝试:

class NilClass
  def hello
    'hello'
  end

  def method_missing *args
    self
  end
end

nil.hello #=> 'hello'
nil.non_existing_method #=> nil

然而,这可能是一个坏主意,因为undefined method for nil是最有用的例外之一 - 它说你在没有预料到的地方就是零。相反,你应该指定你知道nil可能发生的所有地方 - 看看'andand'geon,它完全符合所需:

data.nil? nil : data.process()

# is the same as
data && data.process()

# what justifies the name of the gem
data.andand.process()

更新

有一个非常简单的原因导致无法创建nil的子类--NilClass不允许创建新实例,其new方法未定义(所有不可变类如Fixnum都会遇到同样的问题,Symbol或TrueClass)。所以:

class SuperNil < NilClass
  def method_missing
    self
  end
end

不会引发任何问题,但您将无法创建此类的任何实例。

答案 1 :(得分:3)

在您的情况下扩展NilClass无济于事,因为当您编写nil时,您可以使用NilClass的实例,并且无法覆盖此情况。这意味着你的课程永远不会被使用。

您需要修改NilClass或父类。在Ruby中,一切都是对象,包括nil。然后,您可以修改对象以引入一些自定义行为。

这是ActiveSupport #try method采用的方法。

def try(*a, &b)
  if a.empty? && block_given?
    yield self
  else
    __send__(*a, &b)
  end
end

允许你缩短

@person && @person.name

@person ? @person.name : nil

@person.try(:name)

我的建议是不要滥用这种技术。有时编写较少的代码并不一定是好事。

答案 2 :(得分:2)

您是否考虑过使用辅助方法?例如,而不是

results = data.nil? nil : data.process()
output  = results.nil? nil : results.format(1,2)
你可以写

results = execute(data, :process)
output  = execute(results, :format, 1, 2)

,其中

def execute(receiver, *args)
  return nil unless receiver
  receiver.send(*args)
end

两个例子:

a = [1,2,3]
b = nil
execute(a, :max)       # => 3
execute(b, :max)       # => nil

execute(a, :delete, 2) # => 2, a => [1,3]
execute(b, :delete, 2) # => nil

答案 3 :(得分:1)

您可以使用Ruby 2.3.0中引入的safe navigation operator

output = data&.process&.format

类似地,您可以使用{2.3}中引入的Hash#dig

output = data.dig(:process, :format)