Define_method(): why is the method unable to see $1?

class DataSource
def get_cpu_info
“cpu8001”
end
def get_cpu_price
101
end

def get_mouse_info
    "mouse241"
end
def get_mouse_price
    40
end

end

DataSource.new.methods.grep(/^get_(.+?)_info$/) do |meth|
puts “—#{meth}—”
end

–output:–
—get_cpu_info—
—get_mouse_info—

class Computer
def initialize(an_id, data_source)
@id = an_id
@ds = data_source

    @ds.methods.grep(/^get_(.+?)_info$/) do

        puts "-->#{$1}<---"

        Computer.send(:define_method, $1.to_sym) do
            puts "****" + $1 + "****"   #***NIL NIL NIL NIL

            info = @ds.send("get_#{$1}_info".to_sym)
            price = @ds.send("get_#{$1}_price".to_sym)

            alert = ""
            if price > 100
                alert = "*"
            end

            return "#{alert}#{info} #{price}"
         end
    end
end

end

comp1 = Computer.new(1, DataSource.new)
puts comp1.mouse

–output:–
Line 32:in +': can't convert nil into String (TypeError) from t.rb:32:inmouse’
from t.rb:52

If I add the line:

name = $1

before the line:

Computer.send(…)

and also replace all the $1’s in the body of the define_method() with
‘name’, then the code works like I want.

On Thu, Feb 24, 2011 at 2:59 PM, 7stud – [email protected]
wrote:

end
—get_cpu_info—
puts “–>#{$1}<—”
end
comp1 = Computer.new(1, DataSource.new)
puts comp1.mouse

–output:–
Line 32:in +': can't convert nil into String (TypeError) from t.rb:32:inmouse’
from t.rb:52

I’m not sure of the specifics, but $1 doesn’t persist outside of the
block created in the grep statement. When you call comp1.mouse, that’s
no longer within that block – the method was defined in it, but once
it was made a method it took on an existence of its own.

Eric C. wrote in post #983739:

On Thu, Feb 24, 2011 at 2:59 PM, 7stud – [email protected]
wrote:

end
—get_cpu_info—
puts “–>#{$1}<—”
end
comp1 = Computer.new(1, DataSource.new)
puts comp1.mouse

–output:–
Line 32:in +': can't convert nil into String (TypeError) from t.rb:32:inmouse’
from t.rb:52

I’m not sure of the specifics, but $1 doesn’t persist outside of the
block created in the grep statement. When you call comp1.mouse, that’s
no longer within that block – the method was defined in it, but once
it was made a method it took on an existence of its own.

$1 is a global variable, so saying it doesn’t persist outside of a
block doesn’t make any sense.

I think what is happening is that the body of the define_method() call
forms a closure around the variable $1. Unfortunately, the problem with
global variables is that other parts of the code can change their value.
In this instance, I think what is happening is that a subsequent
unsuccessful pattern match assigns nil to $1. Here is an example of
that:

arr = [“hello”]

arr.each do |x|
x =~ /h(.)ll/
puts $1 #=>e
end

puts $1 #=>e

“hello” =~ /xxx/

puts $1 #=>nil

There are a lot of other methods in the DataSource class, which are
inherited by all classes, and the last method in the list of methods
traversed by grep() must not be one of the methods I defined, so the
pattern match fails against the last method name, and $1 gets set to
nil. Subsequently, when I call the mouse() method, it reads the current
value of $1, which is nil.

On the other hand, when I assign $1 to a local variable, like ‘name’,
‘name’ does not become a reference to $1–instead ‘name’ gets assigned a
copy of $1’s value at the instant the method is created. Furthermore,
because ‘name’ is a local variable, each method created by
define_method() gets its own ‘name’ variable in the method body.

On Thu, Feb 24, 2011 at 8:04 PM, 7stud – [email protected]
wrote:

$1 is a global variable, so saying it doesn’t persist outside of a
block doesn’t make any sense.

It isn’t actually global. I don’t know the specifics, but I used to
worry
about that too, and found out later it was not necessary.

def meth_a
“a” =~ /(.)/
puts “in a, $1 = #{$1.inspect}”
end

def meth_b
“b” =~ /(.)/
puts “in b, $1 = #{$1.inspect}”
meth_a
puts “in b, $1 = #{$1.inspect}”
end

meth_b

>> in b, $1 = “b”

>> in a, $1 = “a”

>> in b, $1 = “b”

On Thu, Feb 24, 2011 at 8:04 PM, 7stud – [email protected]
wrote:

$1 is a global variable, so saying it doesn’t persist outside of a
block doesn’t make any sense.

I think what is happening is that the body of the define_method() call
forms a closure around the variable $1.

Oh, I think you have it exactly. I was thinking that maybe the
numbered globals weren’t truly global, since I read on
http://www.rubyist.net/~slagell/ruby/globalvars.html that $_ and $~
aren’t actually global.

Josh C. wrote in post #983791:

On Thu, Feb 24, 2011 at 8:04 PM, 7stud – [email protected]
wrote:

$1 is a global variable, so saying it doesn’t persist outside of a
block doesn’t make any sense.

It isn’t actually global. I don’t know the specifics, but I used to
worry
about that too, and found out later it was not necessary.

def meth_a
“a” =~ /(.)/
puts “in a, $1 = #{$1.inspect}”
end

def meth_b
“b” =~ /(.)/
puts “in b, $1 = #{$1.inspect}”
meth_a
puts “in b, $1 = #{$1.inspect}”
end

meth_b

>> in b, $1 = “b”

>> in a, $1 = “a”

>> in b, $1 = “b”

Uh oh. Someone is going to have to explain that one to me. $1 does not
act like a regular global variable:

def meth_a
$global = 10
end

def meth_b
$global = 5
puts $global
meth_a()
puts $global
end

meth_b

–output:–
5
10

On Fri, Feb 25, 2011 at 4:59 AM, 7stud – [email protected]
wrote:

class Computer
def initialize(an_id, data_source)
@id = an_id
@ds = data_source

@ds.methods.grep(/^get_(.+?)_info$/) do

 puts "-->#{$1}<---"

 Computer.send(:define_method, $1.to_sym) do

inside this block, your code references $1 itself.
but what you really want is that $1 be evaluated first before the
block generation.

   puts "#{alert} #{info} #{price}"
  end

end
end

try this (brute force) way just to get the idea,

class Computer
def initialize(an_id, data_source)
@id = an_id
@ds = data_source

   @ds.methods.grep(/^get_(.+?)_info$/) do


      puts "-->#{$1}<---"
      eval <<-EVILHERE
       Computer.send(:define_method, $1.to_sym) do
           #puts "****" + ($1) + "****"   #***NIL NIL NIL NIL

           info = @ds.send("get_#{$1}_info".to_sym)
           price = @ds.send("get_#{$1}_price".to_sym)

           alert = ""
           if price > 100
               alert = "*"
           end

           puts "\#{alert} \#{info} \#{price}"
        end
      EVILHERE
   end

end
end

best regards -botp

An online copy of Programming Ruby says all the match variables are
local to
the current scope:

http://webcache.googleusercontent.com/search?q=cache:TH-QZKH6TfsJ:phrogz.net/programmingruby/language.html+scope+of+ruby+match+variables&cd=3&hl=en&ct=clnk&gl=us&client=firefox-a&source=www.google.com

Here is a simpler example showing that trait of $1:

def meth_a
“a” =~ /(.)/
puts $1
end

meth_a

puts “–>#{$1}<—”

–output:–
a
—><—

So, Eric C. was right: $1 is a local variable.

7stud – wrote in post #983980:

An online copy of Programming Ruby says all the match variables are
local to
the current scope:

http://webcache.googleusercontent.com/search?q=cache:TH-QZKH6TfsJ:phrogz.net/programmingruby/language.html+scope+of+ruby+match+variables&cd=3&hl=en&ct=clnk&gl=us&client=firefox-a&source=www.google.com

Here is a simpler example showing that trait of $1:

def meth_a
“a” =~ /(.)/
puts $1
end

meth_a

puts “–>#{$1}<—”

–output:–
a
—><—

So, Eric C. was right: $1 is a local variable.

But then how do you explain the problem with my code? It appears that
‘name’ is more local than $1. It seems that $1 persists from each
grep() loop to the next, like a global variable, but it is only visible
inside the grep() block.

On Fri, Feb 25, 2011 at 2:53 PM, 7stud – [email protected]
wrote:

worry
about that too, and found out later it was not necessary.
[…]
Uh oh. Someone is going to have to explain that to me. $1 does not act
like a regular global variable:

This is getting more confusing for me. I altered your original script by
adding:

[‘foo’].grep(/f(.)o/)

right before your puts “–>#{$1}<—” (at the end of the definition of
initialize). Obviously that grep was successful; but surprisingly, the
value of $1 remains ‘o’ even outside of the invocation of initialize:

grep_test.rb:30:in block (2 levels) in initialize': undefined methodget_o_info’ for #DataSource:0x82ea38 (NoMethodError)
from grep_test.rb:49:in `’

The whole script:

From question on ruby-talk 2/24/2011

class DataSource
def get_cpu_info
“cpu8001”
end
def get_cpu_price
101
end

def get_mouse_info
“mouse241”
end
def get_mouse_price
40
end
end

class Computer
def initialize(an_id, data_source)
@id = an_id
@ds = data_source

@ds.methods.grep(/^get_(.+?)_info$/) do
  puts "-->#{$1}<---"

  Computer.send(:define_method, $1.to_sym) do
    puts "****" + $1 + "****"   #***NIL NIL NIL NIL

    info = @ds.send("get_#{$1}_info".to_sym)
    price = @ds.send("get_#{$1}_price".to_sym)

    alert = ""
    if price > 100
      alert = "*"
    end

    puts "#{alert} #{info} #{price}"
  end
  puts "-->#{$1}<---"
end
['foo'].grep(/f(.)o/)
puts "-->#{$1}<---"

end
end

comp1 = Computer.new(1, DataSource.new)
puts “$1: #$1”
puts comp1.mouse

On Fri, Feb 25, 2011 at 9:53 PM, 7stud – [email protected]
wrote:

worry
meth_a
Uh oh. Someone is going to have to explain that to me. $1 does not act
like a regular global variable:

$1, $2 etc. look like global variables but they are in fact local to
the scope where they are set. This automatically also means that they
are thread local. Josh’s example demonstrates that nicely.

The reason is to simplify applications which use multiple regular
expression matches - nested and concurrently.

Kind regards

robert

Eric C. wrote in post #984033:

On Fri, Feb 25, 2011 at 2:53 PM, 7stud – [email protected]
wrote:

worry
about that too, and found out later it was not necessary.
[…]
Uh oh. Someone is going to have to explain that to me. $1 does not act
like a regular global variable:

This is getting more confusing for me.

I think your source of confusion has to do with how closures work. I
think the following brief example demonstrates what you are seeing in my
larger script:

def do_stuff
x = ‘hello’

my_proc = Proc.new {puts x}

x = ‘goodbye’

return my_proc
end

p = do_stuff
p.call

–output:–
goodbye

The {puts x} block closes over the variable x–not the value ‘hello’.
At the end of the def, x is still in scope, and its value is changed to
‘goodbye’. Then when the def ends, x goes out of scope, so x can no
longer be changed. That means that inside the {puts x} block, the value
of x is ‘goodbye’.

Note that if a block closes over a global variable, the global variable
can be changed at any time, which causes the output of the block {puts
$x} to reflect the current value of the global $x:

$x = 10

def do_stuff
my_proc = Proc.new {puts $x}
end

p = do_stuff
p.call

$x = ‘hello’
p.call

–output:–
10
hello

On Tue, Mar 1, 2011 at 8:53 PM, 7stud – [email protected] wrote:

Thanks for the response Robert.

As far as I can tell, there is a difference between $1 and a local
variable.

Yes, of course.

The output shows that in all the procs, the value of $1 is the value
produced by the last regex match. Yet a new name variable is created
every time through the each loop, and each proc closes over a different
name variable. So there is a difference between a local variable like
name and $1.

This is true but I never claimed differently. I wrote “they are local
to the scope…” and not “they are local variables”. :slight_smile:

Cheers

robert

Robert K. wrote in post #984624:

On Fri, Feb 25, 2011 at 9:53 PM, 7stud – [email protected]
wrote:

worry
meth_a
Uh oh. Someone is going to have to explain that to me. $1 does not act
like a regular global variable:

$1, $2 etc. look like global variables but they are in fact local to
the scope where they are set.

Thanks for the response Robert.

As far as I can tell, there is a difference between $1 and a local
variable. Take a look at this example:

def test
procs = []

%w[a b c].each do |letter|
letter =~ /(.)/ #sets value of $1
name = letter #sets value of ‘name’

 my_proc = Proc.new do
   puts "name = #{name}"
   puts "$1 = #{$1}"
 end

 procs << my_proc

end

puts “$1 = #{$1}” #$1 is visible outside the each block
#puts “name = #{name}” #error: undefined variable or method ‘name’

return procs
end

arr = test()
puts “$1 = #{$1}” #$1 is not visible outside the def

puts “--------------”

arr.each do |a_proc|
a_proc.call
end

–output:–
$1 = c #inside the def, but outside the each block
$1 = #outside the def

name = a
$1 = c
name = b
$1 = c
name = c
$1 = c

The output shows that in all the procs, the value of $1 is the value
produced by the last regex match. That means all the procs are sharing
the same $1 variable. Yet a new name variable is created
every time through the each block, and the procs each close over a
different name variable.

The results are not the same for the two variables: $1 and name,
so there is some difference between $1 and a local variable, like name.
$1’s scope appears to be the whole def, while name’s scope is only the
each block

$1, $2 etc. look like global variables but they are in fact local to
the scope where they are set.

Aren’t local variables also local to the scope where they are set? In
other words, what defines a scope in ruby? More specifically, how do
you know the extent of $1’s scope?

In my last example, the each block seems to define the scope of the
‘name’ variable. Why doesn’t the each block also define the scope of
the $1 variable–after all the match where $1 was set was inside the
each block?

Thanks.

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs