2012-04-18 2 views
3

define_method демонстрирует следующее поведение:Рубин - define_method и закрытие

class TestClass 
    def exec_block(&block) ; yield ; end 
end 
TestClass.new.send(:exec_block) do ; puts self ; end 
# -> main 
TestClass.send(:define_method, :bing) do ; puts self ; end 
TestClass.new.bing 
# -> <TestClass:...> 

То, что я не понимаю, что блок передается define_method должен быть замыкание. Как таковой он должен (по крайней мере, согласно моему пониманию) зафиксировать значение self как main, как выставлено при звонке exec_block.

Я понимаю, что блок станет телом метода, но я не понимаю причину поведения. Почему блок оценивает разные вещи при использовании с различными методами?

Как я могу воспроизвести поведение блока с помощью define_method для других методов? например, как я мог написать exec_block, чтобы он выводил <TestClass:...> вместо `main '?

+0

Как вы объясните поведение exec_block? Он должен печатать 'main', если' self' не был захвачен. Или мне что-то не хватает? – Norswap

+0

Извините, я написал противоположное тому, что я знаю. Дело в том, что оно печатает 'main', если оно не было захвачено, оно должно печатать' ', что не так. – Norswap

+0

Ницца! Но если я динамически связан, не должен ли он печатать 'TestClass' - это случай' exec_block'? Поскольку блок вызывается 'yield whitin' TestClass'. На самом деле, я не совсем понимаю, что происходит, когда Ruby читает определение блока. Например, сохраняется ли какая-либо информация о 'self', например? (Кроме того, если вы суммируете свои комментарии в ответе, я бы согласился на это.) – Norswap

ответ

5

self захватывается закрытия, как и любой другой переменной. Можно проверить, что при пропускании Proc вокруг различных экземпляров объектов:

class A 
    def exec_block(&block) 
    block.call 
    end 
end 

class B 
    def exec_indirect(&block) 
    A.new.exec_block(&block) 
    end 
end 

block = proc { p self } 
a = A.new; b = B.new 

a.exec_block(&block) # => main 
b.exec_indirect(&block) # => main 

Однако BasicObject#instance_eval и двойники перепривязывают переменную self динамически:

Для того, чтобы установить контекст, переменная self устанавливается в OBJ в то время выполнения кода, давая код доступа к переменных экземпляра OBJ в

Module#define_method в свою очередь, использует instance_eval выполнить связанный с ним блок:

Если указан блок, он используется в качестве тела метода. Этот блок оценивается с помощью instance_eval [...]

Наблюдайте:

A.send(:define_method, :foo, &block) 
a.foo     # => #<A:0x00000001717040> 
a.instance_eval(&block) # => #<A:0x00000001717040> 

С этими знаниями, теперь вы можете переписать ваш exec_block использовать instance_eval:

class A 
    def exec_block(&block) 
    instance_eval(&block) 
    end 
end 

block = proc { p self } 
A.new.exec_block(&block) # => #<A:0x00000001bb9828> 

Как упоминалось ранее, используя instance_eval, кажется, единственный способ выполните экземпляр Proc с измененным контекстом. Его можно использовать для implement dynamic binding в Ruby.

0

Первое - когда вы передаете блок явно методу вместо yield, вы можете использовать block.call. Вторая вещь - внутри exec_block метод замены yield с instance_eval(&block) и вы увидите волшебство;)

Немного больше разъяснений - в первом примере блок ловит локальную область вместе с self переменной, которая указывает на main объекта.

Во втором примере (с define_method) блок будет рассматриваться как новый корпус метода, и он будет оцениваться внутри области объекта с использованием instance_eval. Для получения более подробной информации вы можете проверить: http://apidock.com/ruby/Module/define_method

+1

Итак, вы говорите, что значение 'self' не фиксируется при определении блока. Иначе говоря, 'self' не лексически ограничен. Я прав ? – Norswap

0

Вдохновленный на комментарий Никласа В .:

class TestClass 
    def exec_block(&block) ; yield ; end 
end 
s = self 

TestClass.new.send(:exec_block) do ; puts s ; end 
# -> main 

TestClass.send(:define_method, :bing) do ; puts s ; end 
TestClass.new.bing 
# -> main 
Смежные вопросы