如何在 Ruby 的模块中定义类级宏?

Posted

技术标签:

【中文标题】如何在 Ruby 的模块中定义类级宏?【英文标题】:How to define a class-level macro in a module in Ruby? 【发布时间】:2020-08-09 11:31:27 【问题描述】:

在 Ruby 编程语言中,我正在创建一个带有类级宏的类,如下所示:

class Timer 
  def self.add_time
    def time
      STDERR.puts Time.now.strftime("%H:%M:%S")      
    end 
  end 
end 

类方法add_time在执行时会生成一个time方法。 现在,我可以在另一个类Example 中执行该类级宏,如下所示:

class Example < Timer 
  add_time 
end 

当我现在在 Example 类的实例上调用 time 时,time 方法就如我所愿:

ex = Example.new 
ex.time

并打印当前时间:23:18:38

但现在我想将add_time 宏放在一个模块中,仍然具有相同的整体效果。我尝试使用这样的include

module Timer 
  def self.add_time
    def time
      STDERR.puts Time.now.strftime("%H:%M:%S")      
    end 
  end 
end 

class Example 
  include Timer
  add_time
end 

ex = Example.new 
ex.time

然后我收到一个错误,指出方法 add_time 未在类 Example:NameError: undefined local variable or method ‘add_time’ for Example:Class 上定义。因此,我尝试使用 extend,而不是像这样:

class Example 
  extend Timer
  add_time
end

但它给了我一个类似的错误。

所以问题是:我怎样才能获得与原始示例中相同的效果,其中 Timer 被定义为类,但使用模块代替?

【问题讨论】:

当一个类包含一个模块时,模块的实例方法成为该类的实例方法。当一个类扩展一个模块时,模块的实例方法成为该类的类方法。在执行这两个操作时,模块的模块方法都被忽略了。 @CarySwoveland 所以你的意思是 self.add_time 方法在包含或扩展时会被忽略,因为它不是实例方法而是模块方法? 我可以通过将def add_time 定义为模块的实例方法来使其工作,然后执行Example.include Timer。然后我需要做ex.add_timetime 方法添加到实例ex。但这不是我想要的,因为我希望 time 方法可用于我的 Example 类的所有实例。 是的,回答您的第一条评论。关于第二种,将add_time设为Timer的实例方法,如你所说,然后Example.extend Timeradd_time设为Example的类方法。 @CarySwoveland 我也试过了,但它似乎不起作用。有一个模块Timer 和一个实例方法add_time。然后执行Example.extend Timer 使add_time 成为Example 的类方法。最后,致电Example.add_time。但是,当我这样做时,方法time 被定义为类方法,而不是实例方法。所以ex.time 仍然不起作用。但是,我确实通过巧妙地使用class_eval 找到了一个似乎可行的解决方案。我会把它作为我的解决方案发布。感谢您帮助我找到解决方案。 【参考方案1】:

正如@CarySwoveland 指出的那样,module Timer 中的方法def self.add_time 在包含或扩展类时会被忽略。只有模块的实例方法作为类的实例方法(在包含的情况下)或作为类的类方法(在扩展的情况下)添加到类中。

module Timer 
  def add_time # INSTANCE METHOD !
    def time
      STDERR.puts Time.now.strftime("%H:%M:%S")      
    end 
  end 
end 

所以解决的第一步就是将方法def add_time声明为模块的实例方法。接下来,我们使用该模块扩展类Example,以便将模块的实例方法作为类方法添加到类Example中,我们调用add_time方法:

class Example 
  extend Timer # EXTEND INSTEAD OF INCLUDE
  add_time
end

然而,这还没有达到预期的效果,因为 time 方法现在已作为类方法生成:Example.time 打印当前时间 01:30:37,但是类 @987654333 的实例 ex @看不懂time的方法。

因此,解决方案是将方法def time 生成为实例方法而不是类方法。这可以使用class_eval 来完成,这导致我们得到以下可行的解决方案:

module Timer 
  def add_time # INSTANCE METHOD !
    self.class_eval do # USE class_eval TO DEFINE AN INSTANCE METHOD !
      def time
        STDERR.puts Time.now.strftime("%H:%M:%S")      
      end 
    end 
  end
end 

class Example 
  extend Timer # USE EXTEND TO ADD add_time AS A CLASS METHOD
  add_time
end 

ex = Example.new 
ex.time

【讨论】:

以上是关于如何在 Ruby 的模块中定义类级宏?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Ruby 中使用模块扩展浮点和数组类

ruby/rails:如何确定是不是包含模块?

ruby 如何定义和包含模块

如何在 Ruby 中优化模块方法?

ruby:如何正确地要求(避免循环依赖)

如何在 ruby​​ 中引用子模块的“完整路径”?