我的目标是用其他做额外工作的方法替换String
类中的方法(这是针对一个研究项目)。 这适用于许多方法,方法是在 String
类中编写类似于
alias_method :center_OLD, :center
def center(args*)
r = self.send(*([:center_OLD] + args))
#do some work here
#return something
end
对于某些方法,我还需要处理 Proc,这没有问题。 但是,对于 scan
方法,调用它具有从正则表达式匹配中设置特殊全局变量的副作用。 如前所述,这些变量是线程和方法的局部变量。
不幸的是,一些 Rails 代码调用了使用 $&
变量的scan
。 该变量在我的 scan
方法版本中设置,但由于它是本地的,因此它不会返回到使用该变量的原始调用方。
有谁知道解决这个问题的方法? 如果问题需要澄清,请告诉我。
如果它有帮助,到目前为止我看到的 $&
变量的所有用途都在传递给 scan
函数的 Proc 中,因此我可以获取该 Proc 的绑定。 但是,用户似乎根本无法更改$&
,因此我不知道这将如何帮助。
当前代码
class String
alias_method :scan_OLD, :scan
def scan(*args, &b)
begin
sargs = [:scan_OLD] + args
if b.class == Proc
r = self.send(*sargs, &b)
else
r = self.send(*sargs)
end
r
rescue => error
puts error.backtrace.join("n")
end
end
end
当然,在返回r
之前,我会做更多的事情,但这甚至是有问题的 - 所以为了简单起见,我们将坚持这一点。 作为测试用例,请考虑:
"hello world".scan(/l./) { |x| puts x }
无论有没有我的scan
版本,这都可以正常工作。 对于"香草"String
类,这会产生与
"hello world".scan(/l./) { puts $&; }
也就是说,它打印"ll"和"ld"并返回"hello world"。 使用修改后的字符串类,它打印两个空行(因为$&
nil
),然后返回"hello world"。 如果我们能做到这一点,我会很高兴!
你不能设置$&
,因为它是从最后一个MatchData派生$~
。但是,可以设置$~
,这实际上可以满足您的需求。诀窍是在块绑定中设置它。
该代码的灵感来自 Pathname 的旧 Ruby 实现。
(新代码是C语言,不需要关心Ruby帧局部变量)
class String
alias_method :scan_OLD, :scan
def scan(*args, &block)
sargs = [:scan_OLD] + args
if block
self.send(*sargs) do |*bargs|
Thread.current[:string_scan_matchdata] = $~
eval("$~ = Thread.current[:string_scan_matchdata]", block.binding)
yield(*bargs)
end
else
self.send(*sargs)
end
end
end
线程本地(实际上是光纤本地)变量的保存似乎是不必要的,因为它仅用于传递值,并且线程从不读取除最后一个值集之外的任何其他值。它可能是为了恢复原始值(很可能是nil
,因为变量不存在)。
完全避免线程局部的一种方法是创建一个 $~
的 setter 作为 lambda(但它确实为每个调用创建一个 lambda):
self.send(*sargs) do |*bargs|
eval("lambda { |m| $~ = m }", block.binding).call($~)
yield(*bargs)
end
使用其中任何一个,您的示例都有效!
我编写了简单的代码来模拟这个问题:
"hello world".scan(/l./) { |x| puts x }
"hello world".scan(/l./) { puts $&; }
class String
alias_method :origin_scan, :scan
def scan *args, &b
args.unshift :origin_scan
@mutex ||= Mutex.new
begin
self.send *args do |a|
break if !block_given?
@mutex.synchronize do
p $&
case b.arity
when 0
b.call
when 1
b.call a
end
end
end
rescue => error
p error, error.backtrace.join("n")
end
end
end
"hello world".scan(/l./) { |x| puts x }
"hello world".scan(/l./) { puts $& }
并找到了以下内容。变量的包含更改$&
在:call
函数内部发生,即在包含有效值之前的第 3 步:call
$&
,但在块内部它变为无效值。我想这是由于更改过程/线程上下文期间的奇点堆栈和变量恢复,因为:call
函数可能无法访问:scan
本地状态。
我看到两种变体:第一种是避免在特定函数重定义中使用全局变量,第二种是可以更深入地挖掘 ruby 的来源。