2015-12-06 3 views
13

Учитывая следующий класс:Почему не могут быть вызваны защищенные методы с символом proc?

class Foo 
    def a 
    dup.tap { |foo| foo.bar } 
    end 

    def b 
    dup.tap(&:bar) 
    end 

    protected 

    def bar 
    puts 'bar' 
    end 
end 

Похоже, как Foo#a и Foo#b должны быть эквивалентны, но они не так:

> Foo.new.a 
bar 
=> #<Foo:0x007fe64a951ab8> 

> Foo.new.b 
NoMethodError: protected method `bar' called for #<Foo:0x007fe64a940a88> 

Есть ли причина для этого? Это ошибка?

Проверен на Рубине старт 2.2.3p173

+1

Это отличный вопрос. Будем надеяться, что кто-то может просветить нас. –

+0

Хороший вопрос.Единственное отличие, которое я вижу, это то, что вы вызываете «bar» в локальной переменной в первом блоке, но не вызываете его без переменной с proc. Тем не менее, любопытно узнать, есть ли веские основания для этого. – DaniG2k

+0

Протестировано без переменной 'foo', но все равно. Я думаю, что это должно быть связано с тем, как называются procs, хотя я бы предположил, что эти два метода должны выводить эквивалентные результаты. – DaniG2k

ответ

3

Давайте, отметив, что в Ruby, как вы, наверное, знаете, в методе a заявленных на классе Foo, я могу ссылаться на защищенные методы на любого экземпляраFoo.

Как Ruby определяет, есть ли у нас метод, объявленный в классе Foo? Чтобы понять это, нам придется выкопать внутренности вызова метода. Я буду использовать примеры из версии 2.2 МРТ, но, по-видимому, поведение и реализация одинаковы в других версиях (я бы хотел увидеть результаты тестирования этого на JRuby или Rubinious).

Ruby делает это в rb_call0. Как следует из комментария, self используется для определения того, можем ли мы назвать защищенные методы. self извлекается в rb_call из информации кадра вызова текущего потока. Затем, в rb_method_call_status, мы проверяем, что это значение self имеет тот же класс, на котором определен защищенный метод.

Блоки путают проблему несколько. Помните, что локальные переменные в Ruby-методе захватываются любым блоком, объявленным в этом методе. Это означает, что в блоке self является тем же self, на котором был вызван метод. Давайте посмотрим на пример:

class Foo 
    def give_me_a_block! 
     puts "making a block, self is #{self}" 
     Proc.new do 
      puts "self in block0 is #{self}" 
     end 
    end 
end 

proc = Foo.new.give_me_a_block! 

proc.call 

Запуск этого, мы видим тот же экземпляр Foo одинакова на всех уровнях, даже если мы назвали прок от совершенно другого объекта.

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

Теперь давайте посмотрим, почему proc, созданный с помощью &:bar, не может этого сделать. Когда мы помещаем знак & перед аргументом метода, мы делаем две вещи: поручите ruby ​​передать этот аргумент в виде блока и проинструктируйте его называть to_proc.

Это означает использование метода Symbol#to_proc. Этот метод реализуется в C, но когда мы вызываем метод C, указатель на self на текущий кадр становится приемником этого метода C - в этом случае он становится символом :bar. Итак, мы смотрим на экземпляр foo, мы получили , как будто мы находимся в методе класса Symbol, и мы не можем вызывать защищенный метод.

Это глоток, но, надеюсь, он имеет достаточный смысл. Дайте мне знать, если у вас есть предложения относительно того, как я могу улучшить его!

Смежные вопросы