什么是Ruby2.3之前的安全导航操作符(`&.`)?
我可以找到的关于Ruby的新安全导航操作符( &.
)的每个问题的答案错误地声明obj&.foo
等同于obj && obj.foo
。
很容易certificate这种等价是不正确的:
obj = false obj && obj.foo # => false obj&.foo # => NoMethodError: undefined method `foo' for false:FalseClass
此外,存在多重评估的问题。 用具有副作用的表达式替换obj
表明副作用仅在&&
表达式中加倍:
def inc() @x += 1 end @x = 0 inc && inc.itself # => 2 @x = 0 inc&.itself # => 1
什么是最简洁的2.3之前的obj&.foo
避免了这些问题?
Ruby 2.3中的安全导航操作符几乎与try!
由ActiveSupport添加的方法,减去其块处理。
简化版本可能如下所示:
class Object def try(method, *args, &block) return nil if self.nil? public_send(method, *args, &block) end end
你可以像这样使用它
obj.try(:foo).try(:each){|i| puts i}
此try
方法实现了安全导航操作符的各种细节,包括:
- 如果接收器
nil
,它总是返回nil
,无论nil
是否实际执行了查询的方法。 - 如果非零接收器不支持该方法,则会引发
NoMethodError
。 - 它不会吞噬方法调用的任何exception。
由于语言语义的差异,它不能(完全)实现真正的安全导航操作符的其他function,包括:
-
与安全导航操作符相比,我们的
try
方法总是评估其他参数。 考虑这个例子nil&.foo(bar())
这里,不评估
bar()
。 使用我们的try
方法时nil.try(:foo, bar())
我们总是首先调用
bar
方法,无论我们以后是否使用它调用foo
。 -
obj&.attr += 1
是Ruby 2.3.0中的有效语法,无法使用以前语言版本中的单个方法调用进行模拟。
请注意,在生产中实际实现此代码时,您应该查看Refinements而不是修补核心类。
我认为安全遍历运算符模拟的最相似的方法是Rails的try
方法。 但是不完全一样,我们需要在对象不是nil
时处理这种情况,但也不响应该方法。
如果方法无法评估给定方法,则返回nil。
我们可以通过以下方式重写try
:
class Object def try(method) if !self.respond_to?(method) && !self.nil? raise NoMethodError, "undefined method #{ method } for #{ self.class }" else begin self.public_send(method) rescue NoMethodError nil end end end end
然后它可以以相同的方式使用:
Ruby 2.2及更低版本:
a = nil a.try(:foo).try(:bar).try(:baz) # => nil a = false a.try(:foo) # => NoMethodError: undefined method :foo for FalseClass
相当于Ruby 2.3
a = nil a&.foo&.bar&.baz # => nil a = false a&.foo # => NoMethodError