定义一个Ruby中的闭包方法

时间:2010-03-22 20:38:23

标签: ruby closures

我在ruby中的对象中重新定义一个方法,我需要将new方法作为闭包。例如:

def mess_it_up(o)
  x = "blah blah"

  def o.to_s
    puts x  # Wrong! x doesn't exists here, a method is not a closure
  end
end

现在,如果我定义一个Proc,它就是一个闭包:

def mess_it_up(o)
  x = "blah blah"

  xp = Proc.new {||
    puts x  # This works
  end

  # but how do I set it to o.to_s.

  def o.to_s
    xp.call  # same problem as before
  end
end

任何想法怎么做?

感谢。

3 个答案:

答案 0 :(得分:23)

这是有效的(在irb中测试):

注意:这仅更改str - 而不是所有String实例。请阅读以下详细信息,了解其工作原理

another_str = "please don't change me!"
str =         "ha, try to change my to_s! hahaha!"
proc = Proc.new { "take that, Mr. str!" }

singleton_class = class << str; self; end

singleton_class.send(:define_method, :to_s) do
  proc.call
end

puts str.to_s         #=> "take that, Mr. str!"
puts another_str.to_s #=> "please don't change me!"

# What! We called String#define_method, right?

puts String           #=>  String
puts singleton_class  #=>  #<Class:#<String:0x3c788a0>>

# ... nope! singleton_class is *not* String
# Keep reading if you're curious :)

这是有效的,因为你正在打开str singleton class并在那里定义一个方法。因为这个,以及对Module#define_method的调用,有些人称之为“平坦范围”,如果你使用def to_s; 'whatever'; end,你就能够访问超出范围的变量。

你可能想在这里查看其他一些“元编程法术”:

media.pragprog.com/titles/ppmetr/spells.pdf


为什么它只会更改str

因为Ruby有几个有趣的技巧。在Ruby对象模型中,方法调用导致接收者不仅搜索它的类(和它的祖先),而且还搜索它的单例类(或者像Matz所称的那样,它是本征类)。这个单例类允许您[重新]定义单个对象的方法。这些方法称为“单例方法”。在上面的示例中,我们正在这样做 - 定义单例方法名称to_s。它的功能与此完全相同:

def str.to_s
  ...
end

唯一的区别是我们在调用Module#define_method时会使用闭包,而def是一个关键字,这会导致范围发生变化。

为什么它不能更简单?

好吧,好消息是你是用Ruby编程的,所以请随意疯狂:

class Object
  def define_method(name, &block)
    singleton = class << self; self; end
    singleton.send(:define_method, name) { |*args| block.call(*args) }
  end
end


str = 'test'
str.define_method(:to_s) { "hello" }
str.define_method(:bark) { "woof!" }
str.define_method(:yell) { "AAAH!" }

puts str.to_s #=> hello
puts str.bark #=> woof!
puts str.yell #=> AAAH!

而且,如果你很好奇......

你知道课程方法吗?或者,在某些语言中,我们称之为静态方法?好吧,那些确实存在于Ruby中。在Ruby中,类方法实际上只是在Class对象的单例类中定义的方法。

如果这听起来很疯狂,请查看我上面提供的链接。如果你知道如何进行元编程,那么很多Ruby的功能都只能被挖掘 - 在这种情况下你真的想知道单例类/方法,更一般地说,知道Ruby对象模型。

HTH

- 查尔斯

答案 1 :(得分:9)

在Ruby 1.9.2中实现的

Feature #1082使Object#define_singleton_method

成为一项简单的任务
def mess_it_up(o)
  x = "blah blah"

  # Use Object#define_singleton_method to redefine `to_s'
  o.define_singleton_method(:to_s) { x }
end

所涉及的概念仍然与我的previous answer中的概念相同,后者提供了有关如何在Ruby的对象模型中工作的更深入的描述,以及Object#define_method定义这在概念上与Ruby 1.9.2&#39; Object#define_singleton_method相同。

您可能会发现对类似任务有用的其他方法:

答案 2 :(得分:0)

这似乎有效。

class Foo
  def mess_it_up(o)
    x = "blah blah"

    o.instance_variable_set :@to_s_proc, Proc.new { puts x }
    def o.to_s
      @to_s_proc.call
    end
  end
end

var = Object.new
Foo.new.mess_it_up(var)

var.to_s

问题是def中的代码在运行之前和新范围内都不会被评估。因此,您必须先将块保存到对象上的实例变量中,然后再将其解除。

并且define_method不起作用,因为它是一个类方法,这意味着您必须在对象的类上调用它,将该代码提供给该类的所有实例,而不仅仅是此实例。 / p>