2015-11-14 3 views
8

Method#unbind возвращает UnboundMethod ссылку на метод, который впоследствии может быть привязан к другому объекту с использованием UnboundMethod#bind.В чем смысл механизма развязывания метода Руби?

class Foo 
    attr_reader :baz 

    def initialize(baz) 
    @baz = baz 
    end 
end 

class Bar 
    def initialize(baz) 
    @baz = baz 
    end 
end 

f = Foo.new(:test1) 
g = Foo.new(:test2) 
h = Bar.new(:test3) 
f.method(:baz).unbind.bind(g).call # => :test2 
f.method(:baz).unbind.bind(h).call # => TypeError: bind argument must be an instance of Foo 

Первоначально я думал, что это невероятно удивительным, потому что я ожидал, что он будет работать так же, как в JavaScript Function.prototype.call()/Function.prototype.apply(). Однако объект, к которому вы хотите привязать метод , должен быть одного класса.

Единственное приложение, о котором я могу думать, - это отменить метод, потерять исходную реализацию (переопределить метод в исходном или одноэлементном классе), а затем повторить и вызвать его.

+1

Я нашел изящное объяснение здесь - http://blog.jayfields.com/2006/12/ruby-alias-method-alternative.html –

+0

@WandMaker, отличная идея. Он относится к категории, которую я описал. Я был бы удивлен, если это единственная причина его существования. – ndn

ответ

0

Метод и типы UnboundMethod предполагают, что цель привязки должна быть подклассом исходного класса, в котором вы ссылались на метод. Однако метод имеет метод #to_proc, и с этим вы можете избавиться от ограничения типа «тот же тип класса».

Вы должны использовать метод #send, так как #define_method является приватным (вы не можете называть его напрямую).

class A 
    def hoge ; "hoge" ; end 
end 

class B ; end 

hoge = A.new.method(:hoge) 

B.send(:define_method, :hoge_in_b, &hoge) #converting to proc 

b = B.new 
puts b.hoge_in_b 
+0

Во-первых, это будет оценивать блок в контексте исходного объекта (также как и объект A.new, а не 'B.new'). Во-вторых, это не отвечает на вопрос о том, что является точкой развязывания механика. – ndn

+0

Конечно, он будет оценивать его в контексте A.new, потому что это «закрытие».Не существует языка высокого уровня, который использует функции как объекты первого класса, но использует только имя переменной 'name' и оценивает их в другом контексте (кроме' eval'). Таким образом, вы можете использовать переменную в контексте 'A', которая не существует в контексте' B'. И точка, если развязывающий механик? Это похоже на реализацию «Proc, Lambda или Block» в Ruby. Они делают то же самое, только с небольшими различиями. UnboundMethod - это лишь один из многих инструментов со своими собственными свойствами (и его недостатками). – karatedog

+0

не существует внутренней необходимости закрытия, чтобы содержать переменные экземпляра. Блоки и procs - очень разные вещи. Один - это синтаксис, другой - действительный объект. Что касается регулярных процессов против lambdas - я использую их часто. Это означает, что даже если различия тонкие, они достаточно значительны, так что каждая конструкция имеет свою собственную нишу. Вы правы, что Ruby поставляется с множеством механиков, которые человек почти никогда не будет использовать в своей производственной кодовой базе. Тем не менее, даже с вещами, такими как бросок/улов, волокна и глобальные переменные, я могу хотя бы увидеть, откуда они взялись. – ndn

2

Я подведю итоги использования, которое я нашел до сих пор. Однако он не использует unbind.


Во-первых, перезапись метод, используя старую реализацию.Source, благодаря @WandMaker.

Пусть говорят, что вы хотите сделать что-то вроде этого:

class Foo 
    alias old_bar bar 

    def bar 
    old_bar 
    some_additional_processing 
    end 
end 

Это будет работать, но оставит позади old_bar, который не является чем-то желательным. Вместо этого можно было бы сделать:

class Foo 
    old_bar = instance_method(:bar) 

    define_method(:bar) do 
    old_bar.bind(self).call 
    some_additional_processing 
    end 
end 

Во-вторых, вызов другой вариант осуществления способа из иерархии.Source.

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

method(:foo).source_location 

Однако, это не будет работать, если текущий экземпляр реализует метод method, как и в случае с ActionDispatch::Request. В этом случае вы можете сделать:

Kernel.instance_method(:method).bind(self).call(:foo).source_location