What is the pre-Ruby2.3 equivalent to the safe navigation operator (`&.`)?

社会主义新天地 提交于 2019-12-10 18:40:07

问题


The answers to every question I can find (Q1, Q2) regarding Ruby's new safe navigation operator (&.) wrongly declare that obj&.foo is equivalent to obj && obj.foo.

It's easy to demonstrate that this equivalence is incorrect:

obj = false
obj && obj.foo  # => false
obj&.foo        # => NoMethodError: undefined method `foo' for false:FalseClass

Further, there is the problem of multiple evaluation. Replacing obj with an expression having side effects shows that the side effects are doubled only in the && expression:

def inc() @x += 1 end

@x = 0
inc && inc.itself  # => 2

@x = 0
inc&.itself        # => 1

What is the most concise pre-2.3 equivalent to obj&.foo that avoids these issues?


回答1:


The safe navigation operator in Ruby 2.3 works almost exactly the same as the try! method added by ActiveSupport, minus its block handling.

A simplified version of that could look like this:

class Object
  def try(method, *args, &block)
    return nil if self.nil?
    public_send(method, *args, &block)
  end
end

You can use this like

obj.try(:foo).try(:each){|i| puts i}

This try method implements various details of the safe navigation operator, including:

  • It always returns nil if the receiver is nil, regardless of whether nil actually implements the queried method or not.
  • It raises a NoMethodError if the non-nil receiver doesn't support the method.
  • It doesn't swallow any exceptions on method calls.

Due to differences in language semantics, it can not (fully) implement other features of the real safe navigation operator, including:

  • Our try method always evaluates additional arguments, in contrast to the safe navigation operator. Consider this example

    nil&.foo(bar())
    

    Here, bar() is not evaluated. When using our try method as

    nil.try(:foo, bar())
    

    we always call the bar method first, regardless of whether we later call foo with it or not.

  • obj&.attr += 1 is valid syntax in Ruby 2.3.0 which can not be emulated with just a single method call in previous language versions.

Note that when actually implementing this code in production, you should have a look at Refinements instead of patching core classes.




回答2:


I think the most similar method that the safe traversal operators emulate is Rails' try method. However not exactly, we need to handle the case when the object is not nil but also does not respond to the method.

Which will return nil if the method can not evaluate the given method.

We can rewrite try pretty simply by:

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

Then it can be used in much the same way:

Ruby 2.2 and lower:

a = nil
a.try(:foo).try(:bar).try(:baz)
# => nil

a = false
a.try(:foo)
# => NoMethodError: undefined method :foo for FalseClass

Equivalent in Ruby 2.3

a = nil
a&.foo&.bar&.baz
# => nil

a = false
a&.foo
# => NoMethodError


来源:https://stackoverflow.com/questions/34602054/what-is-the-pre-ruby2-3-equivalent-to-the-safe-navigation-operator

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!