什么是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