代码之家  ›  专栏  ›  技术社区  ›  Pablo Fernandez

在Ruby中定义一个闭包方法

  •  9
  • Pablo Fernandez  · 技术社区  · 14 年前

    我在Ruby中的一个对象中定义了一个方法,我需要这个新方法作为一个闭包。例如:

    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
    

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

    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 回复  |  直到 9 年前
        1
  •  23
  •   dfjacobs    14 年前

    本工程(在IRB中测试):

    注意:此项仅作更改 str -不是所有的字符串实例。请阅读下面的内容,了解这项工作的原因

    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's singleton class 在那里定义一个方法。因为这个,以及打电话给 Module#define_method ,有一些人称之为“平面范围”,如果使用 def to_s; 'whatever'; end .

    您可以在这里查看其他一些“元编程拼写:

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


    为什么它只会改变 STR ?

    因为鲁比有几个有趣的把戏。在Ruby对象模型中,方法调用不仅会导致Receiver搜索它的类(和 它的 祖先),但它也是单子类(或者如Matz所说,它是本征类)。这个singleton类允许您为单个对象[重新]定义一个方法。这些方法称为“单例方法”。在上面的示例中,我们只是这样做-定义一个单例方法名 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中,类方法实际上只是类对象的singleton类中定义的方法。

    如果这一切听起来很疯狂,看看我上面提供的链接。只有知道如何进行元编程,才能充分利用Ruby的强大功能——在这种情况下,您真的需要了解单例类/方法,更一般地说,还需要了解Ruby对象模型。

    高温高压

    -查尔斯

        2
  •  9
  •   Community Egal    7 年前

    Feature #1082 在Ruby1.9.2中实现使这项任务变得简单 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 概念上与Ruby1.9.2相同的定义 Object#define_singleton_method .

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

        3
  •  0
  •   Alex Wayne    14 年前

    这似乎奏效了。

    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 不起作用,因为它是一个类方法,这意味着您必须在对象的类上调用它,将代码赋给该类的所有实例,而不仅仅是这个实例。