Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using $1, $2, etc. global variables inside method definition

Given the following two pieces of code:

def hello(z)
  "hello".gsub(/(o)/, &z)
end
z = proc {|m| p $1}
hello(z)
# prints: nil

def hello
  z = proc {|m| p $1}
  "hello".gsub(/(o)/, &z)
end
hello
# prints: "o"

Why are the outputs of these two pieces of code different? Is there a way to pass a block to gsub from outside of the method definition so that the variables $1, $2 would be evaluated in the same way as if the block was given inside the method definition?

like image 843
Bogdan Gusiev Avatar asked Aug 31 '13 16:08

Bogdan Gusiev


1 Answers

Why the output is different?

A proc in ruby has lexical scope. This means that when it finds a variable that is not defined, it is resolved within the context the proc was defined, not called. This explains the behavior of your code.

You can see the block is defined before the regexp, and this can cause confusion. The problem involves a magic ruby variable, and it works quite differently than other variables. Citing @JörgWMittag

It's rather simple, really: the reason why $SAFE doesn't behave like you would expect from a global variable is because it isn't a global variable. It's a magic unicorn thingamajiggy.

There are quite a few of those magic unicorn thingamajiggies in Ruby, and they are unfortunately not very well documented (not at all documented, in fact), as the developers of the alternative Ruby implementations found out the hard way. These thingamajiggies all behave differently and (seemingly) inconsistently, and pretty much the only two things they have in common is that they look like global variables but don't behave like them.

Some have local scope. Some have thread-local scope. Some magically change without anyone ever assigning to them. Some have magic meaning for the interpreter and change how the language behaves. Some have other weird semantics attached to them.

If you are really up to find exactly how the $1 and $2 variables work, I assume the only "documentation" you will find is rubyspec, that is a spec for ruby done the hard way by the Rubinus folks. Have a nice hacking, but be prepared for the pain.


Is there a way to pass a block to gsub from another context with $1, $2 variables setup the right way?

You can achieve what you want with this following modification (but I bet you already know that)

require 'pp'
def hello(z)
  #z = proc {|m| pp $1}
  "hello".gsub(/(o)/, &z)
end
z = proc {|m| pp m}
hello(z)

I'm not aware of a way to change the scope of a proc on the fly. But would you really want to do this?

like image 91
fotanus Avatar answered Oct 19 '22 23:10

fotanus