以编程方式使用$&的别名方法全局变量

时间:2015-05-28 17:09:43

标签: ruby global-variables metaprogramming

我试图使用Ruby的特殊$&returns last regex match)的方法进行别名。我可以手动执行此操作,它可以工作:

original = String.instance_method(:sub)
String.send(:define_method, :sub) do |*args, &block|
  puts "called"
  original.bind(self).call(*args, &block)
end
"foo".sub(/f/) { $&.upcase }
  called
  # => "Foo"

但是,如果我尝试编写一个为我执行此操作的方法,则会失败:

def programatic_alias(klass, method_name)
  original = klass.instance_method(method_name)
  klass.send(:define_method, method_name) do |*args, &block|
    puts "called"
    original.bind(self).call(*args, &block)
  end
end

programatic_alias(String, :sub)
"foo".sub(/f/) { $&.upcase }
  called
  NoMethodError: undefined method `upcase' for nil:NilClass
  called
  called
  called
    from (irb):19:in `block in irb_binding'

看起来全球状态受到programatic_alias方法范围的影响,但我不确定这是不是发生了什么。问题是这样的:我如何以编程方式为String#sub设置别名,以便它仍然适用于Ruby的特殊全局变量?

1 个答案:

答案 0 :(得分:4)

据我所知,你不能这样做。 docs

  

这些全局变量是线程局部变量和方法局部变量。

如果您深入了解ruby来源,请访问$&来自last_match_getterrb_backref_get来自vm_svar_get的数据,该Repeater会调用DataList(跳过更多内部方法)获取当前控制帧并从那里读取数据。这些数据都没有暴露给ruby api - 没有办法将这些数据从一个帧传播到你想要访问它的那个。

在第二个示例中,对原始方法的调用发生在programatic_alias方法中,因此在该范围内设置了$&。出于同样的原因

'foo'.try(:sub, /f/) {$&.upcase}

也不起作用。

你的第一个例子是有效的,因为调用sub的地方和引用$&的地方(在块内)都在同一方法范围内(在本例中为ruby顶级) 。将其更改为:

original = String.instance_method(:sub)
String.send(:define_method, :sub) do |*args, &block|
  puts "called"
  original.bind(self).call(*args, &block)
end

def x
  "foo".sub(/f/) { $&.upcase }
end

x()
您的块中不再定义

$&(如果您发现x引发的异常,则可以看到$&已设置在顶层)