问题
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 isnil
, regardless of whethernil
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 examplenil&.foo(bar())
Here,
bar()
is not evaluated. When using ourtry
method asnil.try(:foo, bar())
we always call the
bar
method first, regardless of whether we later callfoo
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