为什么 `gsub` 调用 `to_hash`?

Why does `gsub` call `to_hash`?

我正在写 DSL。我不希望用户必须引用参数来传递字符串,因此我覆盖 method_missing 以将未知方法转换为字符串。在下面的示例中,create 是 DSL 方法,我希望用户键入不带引号的 arg1arg2

def method_missing(m, *arg)
  m.to_s
end
def create(*args)
  arg1.gsub(#do something here)
end

create arg1 arg2

但是,当我在 'string':

上使用 gsub 时,这会引发错误
'gsub': can't convert String to Hash (String#to_hash gives String) (TypeError)

我猜 method_missing 覆盖搞砸了,因为看起来 gsub 正在调用 String#to_hash,这不是 String 中的方法,因此它是路由到 method_missing.

我想知道为什么 gsub 调用 String#to_hash,或者是否有任何其他方法可以让 DSL 用户不必键入引号,而不会覆盖 method_missing

gsub 可能在某处使用 method_missing 本身,因此似乎在全局定义它会导致方法调用出现内部问题。如果你打算使用 method_missing 确保你总是在模块或 class:

中定义它
module CoolDSL
  def self.method_missing(m, *arg)
    m.to_s
  end

  def self.create(*args)
    args[0].gsub(/1/, "2")
  end

  def self.do_thing
    create arg1 arg2
  end
end

CoolDSL.do_thing

当然,这作为 DSL 并不是很有用,因此您需要了解 instance_evalyield 的强大功能。我喜欢 this guide.

String#gsub 根据参数数量和类型做不同的事情,如果给出一个块:

gsub(pattern, replacement) → new_str
gsub(pattern, hash) → new_str
gsub(pattern) {|match| block } → new_str
gsub(pattern) → enumerator

第二个记录为:

If the second argument is a Hash, and the matched text is one of its keys, the corresponding value is the replacement string.

但是怎么和第一个区别呢?两者都有两个参数!这有点复杂,但在你的情况下 Ruby (好吧,参考实现称为 CRuby 或确切地说是 MRI)首先检查第二个参数是否具有内部类型 T_HASH (由于 #to_s),它很可能 T_STRING),然后检查是否可以调用 #to_hash。要么因为它响应它,要么 #method_missing 可以代替。您已经定义了它,因此 Ruby 调用了它。但是它没有 return T_HASH,这就是您发布的异常的原因。

一个可能的解决方案是定义 main.method_missing 而不是 Object#method_missing(因为 String 继承自 Object):

def self.method_missing(m, *arg)
  m.to_s
end

不过,如果这种文件不符合 Ruby 的语法,我建议坚持使用引号或编写您自己的小型解析器。使用 *_missing 可能会导致出现混乱或无用的错误消息。甚至 none(我猜 create arg1 arg2 应该是 create arg1, arg2)。