Проблемы с уточнением Ruby с помощью response_to? и охват

Я пытаюсь добавить метод экземпляра foo в класс Ruby Array, поэтому при его вызове строковые элементы массива заменяются на строку «foo».

Это можно легко сделать, исправив классы Ruby String и Array.

class String
  def foo
    replace "foo"
  end
end

class Array
  def foo
    self.each {|x| x.foo if x.respond_to? :foo }
  end
end

a = ['a', 1, 'b']
a.foo
puts a.join(", ")   # you get 'foo, 1, foo' as expected

Теперь я пытаюсь переписать приведенное выше, используя функцию уточнений Ruby 2< /а>. Я использую Ruby версии 2.2.2.

Работает следующее (в файле, например, ruby ​​test.rb, но почему-то не в irb)

module M
  refine String do
    def foo
      replace "foo"
    end
  end
end

using M
s = ''
s.foo
puts s      # you get 'foo'

Однако я не могу заставить его работать при добавлении foo в класс Array.

module M
  refine String do
    def foo
      replace "foo"
    end
  end
end

using M

module N
  refine Array do
    def foo
      self.each {|x| x.foo if x.respond_to? :foo }
    end
  end
end

using N

a = ['a', 1, 'b']
a.foo
puts a.join(", ")   # you get 'a, 1, b', not 'foo, 1, foo' as expected

Есть две проблемы:

  1. После уточнения класса с помощью нового метода respond_to? не работает, даже если вы можете вызвать этот метод для объекта. Попробуйте добавить puts 'yes' if s.respond_to? :foo в качестве последней строки во втором фрагменте кода, вы увидите, что «да» не печатается.
  2. В моем уточнении массива String#foo выходит за рамки. Если вы удалите if x.respond_to? :foo из массива#foo, вы получите ошибку undefined method 'foo' for "a":String (NoMethodError). Итак, вопрос: как сделать уточнение String#foo видимым внутри уточнения Array#foo?

Как мне преодолеть эти две проблемы, чтобы я мог заставить это работать?

(Пожалуйста, не предлагайте альтернативные решения, не требующие уточнения, потому что это теоретическое упражнение, чтобы я мог научиться использовать уточнение).

Спасибо.


person Zack Xu    schedule 12.08.2015    source источник


Ответы (2)


  1. Метод respond_to? не работает, и это задокументировано здесь.

  2. Проблема в том, что вы можете активировать уточнение только на верхнем уровне, и они являются лексическими по объему.

Одним из решений будет:

module N
  refine String do
    def foo
      replace 'foobar'
    end
  end

  refine Array do
    def foo
      self.each do |x|
        x.foo rescue x
      end
    end
  end
end

using N

a = ['a', 1, 'b']
p a.foo

puts a.join(", ") # foo, 1, foo
person daLizard    schedule 12.08.2015

Снова возвращаясь к вашему примеру, простым решением может быть переопределение response_to? метод в блоке уточнения:

module M
  refine String do
    def foo
      replace "foo"
    end
    def respond_to?(name,all=false)
      list_methods = self.methods.concat [:foo]
      list_methods.include? name
    end
  end

  refine Array do
    def foo
      self.each {|x| x.foo if x.respond_to? :foo }
    end
  end

end

using M

a = ['a', 1, 'b']
a.foo
puts a.join(", ")    # you get 'foo, 1, foo'
person MVP    schedule 18.04.2018