( returns the l...">

A program alias method that uses the $ & Global Variable variable

I am trying to use an alias of a method that uses Ruby special $& ( returns the last regular expression match ). I can do it manually and it works:

 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" 

However, if I try to write a method that does this for me, it fails:

 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' 

The global state seems to depend on the scope of the programatic_alias method, but I'm not sure if this is happening. The questions are: how can I programmatically create a String#sub alias so that it still works with Ruby special global variables?

+8
ruby global-variables metaprogramming
source share
1 answer

As far as I know, you cannot do this. docs say

These global variables are local and local flow variables.

If you dig into the ruby ​​source, accessing $& calls last_match_getter , which gets its data from rb_backref_get , which calls vm_svar_get , which (skipping a few more internal methods) gets the current control frame and reads the data from there. None of this data is displayed in ruby ​​api - there is no way to distribute this data from one frame to the one you want to access.

In your second example, the call to the original method takes place inside your programatic_alias method, and therefore $& set in this area. For the same reason

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

will not work either.

Your first example works halfway because the place where sub is called and the place that $& refers to (inside the block) is in the same area of ​​the method (in this case, the top ruby ​​level). Change it to:

 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() 

and $& no longer defined in your block (if you catch the exception thrown by x , you can see that $& set at the top level)

+4
source share

All Articles