Что такое pre-Ruby2.3 эквивалентный оператору безопасной навигации (`&.`)?
Ответы на каждый вопрос, который я могу найти ( Q1, Q2) относительно нового оператора безопасной навигации в Ruby (&.
) ошибочно заявлять, что obj&.foo
эквивалентно obj && obj.foo
,
Легко показать, что эта эквивалентность неверна:
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
что позволяет избежать этих проблем?
2 ответа
Оператор безопасной навигации в 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
если неnil
получатель не поддерживает метод. - Он не проглатывает никаких исключений при вызовах методов.
Из-за различий в семантике языка он не может (полностью) реализовать другие функции реального оператора безопасной навигации, включая:
наш
try
Метод всегда оценивает дополнительные аргументы, в отличие от оператора безопасной навигации. Рассмотрим этот примерnil&.foo(bar())
Вот,
bar()
не оценивается. При использовании нашегоtry
метод какnil.try(:foo, bar())
мы всегда называем
bar
метод первым, независимо от того, будем ли мы позже вызыватьfoo
с этим или нет.obj&.attr += 1
допустимый синтаксис в Ruby 2.3.0, который не может быть эмулирован с помощью одного вызова метода в предыдущих языковых версиях.
Обратите внимание, что при фактической реализации этого кода в производстве вам следует взглянуть на уточнения, а не на исправление основных классов.
Я думаю, что наиболее похожим методом, который эмулируют операторы безопасного обхода, является Rails try
метод. Однако не совсем, нам нужно обработать случай, когда объект не 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