【问题标题】:What is the point of Ruby's method unbinding mechanism?Ruby 的方法解除绑定机制有什么意义?
【发布时间】:2016-02-15 22:48:03
【问题描述】:

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()。但是,您要将方法绑定到的对象必须属于同一类

我能想到的唯一应用是如果你取消绑定一个方法,失去原来的实现(在原来的或单例类中重新定义方法)然后重新绑定并调用它。

【问题讨论】:

标签: ruby methods metaprogramming


【解决方案1】:

Method 和 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

【讨论】:

  • 首先,这将在原始对象(又名A.new 对象,而不是B.new 对象)的上下文中评估块。其次,这并没有回答解绑机制的意义何在。
  • 当然它会在A.new的上下文中评估它,因为它是一个closure。没有高级语言将函数用作第一类对象,而仅使用变量的name 并在不同的上下文中评估它们(eval 除外)。这样,您可以在上下文A 中使用一个在上下文B 中不存在的变量。如果解除绑定机制的重点是什么?这就像 Ruby 中的 Proc, Lambda or Block 实现的要点。他们做同样的事情,只有很小的差异。 UnboundMethod 只是众多工具中的一种,它有自己的属性(和缺点)。
  • 闭包并不需要包含实例变量。块和触发是非常独立的东西。一个是语法,另一个是实际对象。至于常规 procs 与 lambdas - 我经常使用两者。这意味着即使差异很小,它们也足够重要,以至于每个构造都有自己的利基。你是对的,Ruby 带有很多机制,一个人几乎不会在他的生产代码库中使用这些机制。尽管如此,即使有 throw/catch、纤维和全局变量之类的东西,我至少可以看到它们来自哪里。
  • 闭包的定义是在创建闭包时包含局部范围的变量(对它们的引用)。没有它,就不是关闭。如果其中的所有变量都丢失了,那段代码甚至将不再工作。
  • 在此处检查方法包装模式:stackoverflow.com/a/4471202/216248。它使用 UnboundMethod 来获取方法的旧版本,因此可以修改“实际”版本(作为本地 super 工作)
【解决方案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

【讨论】:

    猜你喜欢
    • 2013-04-24
    • 1970-01-01
    • 2013-05-15
    • 1970-01-01
    • 1970-01-01
    • 2019-05-14
    • 2014-05-05
    • 2011-12-24
    • 1970-01-01
    相关资源
    最近更新 更多