Что такое 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
Другие вопросы по тегам