我正在尝试为应用程序扩展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扩展的对象?它会评估为假吗?
答案 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)