【问题标题】:Ruby metaprogramming: cannot send a method to a moduleRuby 元编程:无法将方法发送到模块
【发布时间】:2017-02-04 05:36:53
【问题描述】:

例如我有以下自定义类和模块:

module SimpleModule
  def hello_world
    puts 'i am a SimpleModule method'
  end

  def self.class_hello_world
    puts 'i am a SimpleModule class method'
  end
end

class SimpleClass
  def hello_world
    puts 'i am SimpleClass method'
  end

  def self.class_hello_world
    puts 'i am a SimpleClass class method'
  end
end

我尝试使用方法send在类和模块中调用这些方法

SimpleClass.send(class_hello_world)  # work
SimpleClass.new.send(hello_world)    # work
SimpleModule.send(class_hello_world) # work
SimpleModule.new.send(hello_world)   # not work
SimpleModule.send(hello_world)       # not work

换句话说,我不知道如何从SimpleModule 调用hello_world。如果该方法之前用 self 定义过,这是可能的。

我需要这样做是因为我想实现一个“自定义包含”:它包含从模块到另一个类的所有方法。

请告诉我该怎么做。

【问题讨论】:

  • SimpleModule.new.send(hello_world) 不起作用,因为您不能拥有模块的实例。
  • @EddeAlmeida 谢谢。所以Ruby通过它的语言实现关键字“include”或者我可以通过使用元编程来做类似的事情?如果元编程可能,应该有一种从模块调用方法的方法。

标签: ruby metaprogramming


【解决方案1】:

五个陈述

让我们一次一个地考虑这五个语句(但顺序与所示顺序不同)。注意send的参数必须是方法名,用字符串或符号表示。

SimpleModule.send("class_hello_world")
  # i am a SimpleModule class method

这是正常的,尽管这些方法通常称为模块方法。一些常见的内置模块,例如Math,只包含模块方法。

SimpleClass.send(:class_hello_world)
  # i am a SimpleClass class method

由于类是一个模块,其行为与上述相同。 class_hello_world 通常被称为类方法

SimpleClass.new.send(:hello_world)
  # i am SimpleClass method

这是实例方法的正常调用。

SimpleModule.send("hello_world")
  #=> NoMethodError: undefined method `hello_world' for SimpleModule:Module

没有模块方法hello_world

SimpleModule.new.send(hello_world)
  #=> NoMethodError: undefined method `new' for SimpleModule:Module

不能创建模块的实例。

includeprepend

假设有人写了

SimpleClass.include SimpleModule
  #=> SimpleClass
SimpleClass.new.hello_world
  # i am SimpleClass method

所以SimpleClass'原始方法hello_world不会被同名的模块方法覆盖。考虑SimpleClass'的祖先。

SimpleClass.ancestors
  #=> [SimpleClass, SimpleModule, Object, Kernel, BasicObject]

Ruby 将在SimpleClass 中查找hello_world——并找到它——然后再考虑SimpleModule

但是,可以使用Module#prependSimpleModule#hello_world 放在SimpleClass#hello_world 之前。

SimpleClass.prepend SimpleModule
  #=> SimpleClass
SimpleClass.new.hello_world
  # i am a SimpleModule method
SimpleClass.ancestors
  #=> [SimpleModule, SimpleClass, Object, Kernel, BasicObject]

绑定未绑定的方法

你还有一件事要做。 SimpleModule 的实例方法(这里只有一个)是未绑定的。您可以使用UnboundMethod#bind 将每个绑定到SimpleClass 的实例,然后使用callsend 执行它。

sc = SimpleClass.new
  #=> #<SimpleClass:0x007fcbc2046010> 
um = SimpleModule.instance_method(:hello_world)
  #=> #<UnboundMethod: SimpleModule#hello_world> 
bm = um.bind(sc)
  #=> #<Method: SimpleModule#hello_world> 
bm.call
  #=> i am a SimpleModule method
sc.send(:hello_world)
  #=> i am a SimpleModule method

【讨论】:

  • 谢谢。我了解我现在遇到的问题是因为我无法初始化模块。我尝试再次验证关于实现 Ruby 关键字 include 的概念。所以如果我不能像这样调用模块上的方法,我可以通过某种方式吗?
  • 感谢 prepend 关键字。我以前不知道这一点:D 但无论如何,就基于语言的关键字而言,它们仍然不像 include 。 (所以我不能自己实现):D
  • 不知道prepend,我可以看到如果它被滥用会引起很多头痛:D。感谢您的写作!
  • “我可以看到如果误用 ___ 会引起很多麻烦。” Ruby——任何编程语言——提供了很多很多的方法来填补这个空白。
  • 如何实现自定义包含/扩展请参考link。我从这篇文章中学到了很多东西:D
【解决方案2】:

模块不能有实例方法,因为它们不是类。如果您在模块中定义实例方法,则称为 mixin。这些“mixin”可以包含在另一个类中,然后可供使用。

完整的解释在这里docs

编辑:

例如,您可以这样做:

module SimpleModule
  def hello_world
    p 'hello world'
  end
end

class Example
  include SimpleModule
end

Example.new.send(:hello_world)

这是调用模块 mixin 的一种方法。

【讨论】:

  • 谢谢。我知道“包含”关键字。我可以自己实现“包含”吗?没有现实世界的应用程序,只是我想演示概念,但我坚持使用模块的调用方法。谢谢
  • 我添加了一个示例,说明如何将模块包含在类中,然后在类上调用 send 以调用模块方法。
【解决方案3】:

因为您似乎想创建自己的包含版本。

查看Module.append_features 文档

当这个模块被包含在另一个模块中时,Ruby 调用 append_features 此模块,将其传递给 mod 中的接收模块。 Ruby 的默认设置 实现是添加常量、方法和模块变量 如果此模块尚未添加到 mod,则将此模块添加到 mod 或其祖先之一。另请参阅 Module#include。

如果你想自己重写include,这就是你必须做的。

由于您喜欢冒险,也许您会喜欢阅读 Ruby 的源代码,看看它是如何在内部实现的。见https://github.com/ruby/ruby...class.c#L853:L934

【讨论】:

  • 感谢您链接到源代码。这很棒。关于附加,我看到我看起来像“包含”,与包含相比在行为上略有不同。所以我不认为使用 append 是重写 include 的一种方式:D
  • 关于源代码链接。你怎么能找到这个?你是 Ruby 的贡献者吗?我只是有点好奇。所以我可以知道我将来如何做同样的事情。谢谢
  • 我使用prypry-docs gem来定位源代码,$命令显示源代码。然后在github.com/ruby/ruby 使用搜索进一步挖掘
【解决方案4】:

您也可以使用constantize:

def call_method(method)
  "SimpleModule::#{method}".constantize
end

【讨论】:

    猜你喜欢
    • 2017-01-03
    • 2017-06-21
    • 1970-01-01
    • 1970-01-01
    • 2012-03-18
    • 2015-06-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多