我的目标是将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
方法,调用它会产生从正则表达式匹配中设置special global variables的副作用。如文档所述,这些变量是线程和方法的本地变量。
不幸的是,一些Rails代码会调用scan
来使用$&
变量。该变量在我的scan
方法版本中设置,但因为它是本地的,所以它不会返回到使用该变量的原始调用者。
有没有人知道解决这个问题的方法?如果问题需要澄清,请告诉我。
如果它有用,我到目前为止看到的$&
变量的所有用法都在Proc传递给scan
函数内部,所以我可以获得该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
版本都可以正常使用。使用“vanilla”String
类,它产生与
"hello world".scan(/l./) { puts $&; }
即,它打印“ll”和“ld”并返回“hello world”。使用修改后的字符串类,它会打印两个空行(因为$&
为nil
),然后返回“hello world”。如果我们能够做到这一点,我会很高兴的!
答案 0 :(得分:4)
您无法设置$&
,因为它来自$~
,即最后一个MatchData。
但是,$~
可以设置,实际上可以做你想要的。
诀窍是在块绑定中设置它。
代码的灵感来自the old Ruby implementation of Pathname (新代码在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
使用其中任何一个,您的示例都有效!
答案 1 :(得分:1)
我编写了模拟问题的简单代码:
"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
函数内部,即在:call
$&
包含有效值之前的第3步,但在块内部变为无效。我想这是由于变换过程/线程上下文中的奇点堆栈和变量恢复,因为,:call
函数可能无法访问:scan
本地状态。
我看到两个变体:第一个是避免在特定函数重定义中使用全局变量,第二个是可能更深入地挖掘ruby源。