why is $1 in a grep() equal to nil?

7

7stud --

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

puts "#{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:in `mouse'
from t.rb:52
 
7

7stud --

If I add the line:

name = $1

before the line:

Computer.send(.....)

and replace all the $1's in the body of the define_method(), then the
code works like I want.
 
E

Eric Christopherson

class DataSource
=A0 =A0def get_cpu_info
=A0 =A0 =A0 =A0"cpu8001"
=A0 =A0end
=A0 =A0def get_cpu_price
=A0 =A0 =A0 =A0101
=A0 =A0end

=A0 =A0def get_mouse_info
=A0 =A0 =A0 =A0"mouse241"
=A0 =A0end
=A0 =A0def get_mouse_price
=A0 =A0 =A0 =A040
=A0 =A0end
end

DataSource.new.methods.grep(/^get_(.*?)_info$/) do |meth|
=A0 =A0puts "---#{meth}---"
end

--output:--
---get_cpu_info---
---get_mouse_info---


class Computer
=A0 =A0def initialize(an_id, data_source)
=A0 =A0 =A0 =A0@id =3D an_id
=A0 =A0 =A0 =A0@ds =3D data_source

=A0 =A0 =A0 [email protected](/^get_(.+?)_info$/) do

=A0 =A0 =A0 =A0 =A0 =A0puts "-->#{$1}<---"

=A0 =A0 =A0 =A0 =A0 =A0Computer.send:)define_method, $1.to_sym) do
=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0puts "****" + $1 + "****" =A0 #***NIL NIL = NIL NIL

=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0info =3D @ds.send("get_#{$1}_info".to_sym)
=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0price =3D @ds.send("get_#{$1}_price".to_sy= m)

=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0alert =3D ""
=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0if price > 100
=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0alert =3D "*"
=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0end

=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0puts "#{alert} #{info} #{price}"
=A0 =A0 =A0 =A0 =A0 =A0 end
=A0 =A0 =A0 =A0end
=A0 =A0end

end



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


--output:--
Line 32:in `+': can't convert nil into String (TypeError)
=A0from t.rb:32:in `mouse'
=A0from 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.
 
7

7stud --

Eric Christopherson wrote in post #983739:
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 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
end

puts $1

"hello" =~ /xxx/

puts $1

--output:--
e
e
nil


There are a lot other methods in the DataSource class that are inherited
by all classes, and the last one in the list must not be one of the
methods I defined, so the pattern match fails against that 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.

Assigning $1 to a local variable, like 'name', means that each method
created by define_method() gets its own distinct 'name' variable in the
method body.
 
E

Eric Christopherson

Eric Christopherson wrote in post #983739:

$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.
 
B

botp

class Computer
=A0 =A0def initialize(an_id, data_source)
=A0 =A0 =A0 =A0@id =3D an_id
=A0 =A0 =A0 =A0@ds =3D data_source

=A0 =A0 =A0 [email protected](/^get_(.+?)_info$/) do

=A0 =A0 =A0 =A0 =A0 =A0puts "-->#{$1}<---"

=A0 =A0 =A0 =A0 =A0 =A0Computer.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.
=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0puts "****" + $1 + "****" =A0 #***NIL NIL = NIL NIL

=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0info =3D @ds.send("get_#{$1}_info".to_sym)
=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0price =3D @ds.send("get_#{$1}_price".to_sy= m)

=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0alert =3D ""
=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0if price > 100
=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0alert =3D "*"
=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0end

=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0puts "#{alert} #{info} #{price}"
=A0 =A0 =A0 =A0 =A0 =A0 end
=A0 =A0 =A0 =A0end
=A0 =A0end


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

class Computer
def initialize(an_id, data_source)
@id =3D an_id
@ds =3D 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 =3D @ds.send("get_#{$1}_info".to_sym)
price =3D @ds.send("get_#{$1}_price".to_sym)

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

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


best regards -botp
 
J

Josh Cheek

[Note: parts of this message were removed to make it a legal post.]

$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"
 
7

7stud --

Josh Cheek wrote in post #983791:
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 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
 
7

7stud --

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.c...&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 Christopherson 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's value is only
visible inside the block.
 
E

Eric Christopherson

Josh Cheek wrote in post #983791:
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.
[...]
Uh oh. =A0Someone is going to have to explain that to me. =A0$1 does not = act
like a regular global variable:

This is getting more confusing for me. I altered your original script by ad=
ding:

['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 method
`get_o_info' for #<DataSource:0x82ea38> (NoMethodError)
from grep_test.rb:49:in `<main>'

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 =3D an_id
@ds =3D 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 =3D @ds.send("get_#{$1}_info".to_sym)
price =3D @ds.send("get_#{$1}_price".to_sym)

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

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

comp1 =3D Computer.new(1, DataSource.new)
puts "$1: #$1"
puts comp1.mouse
 
7

7stud --

Eric Christopherson wrote in post #984033:
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 is how closures work. I believe the
following brief example demonstrates what you are seeing in my larger
script:

def do_stuff
x = 'hello'

p = Proc.new {puts x}

x = 'goodbye'

return p
end

my_callable = do_stuff
my_callable.call

--output:--
goodbye

The block {puts x} closes over the *variable* x--not the value 'hello'.
At the end of the def, x is still in scope and it 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 block {puts x}, the value
of x is 'goodbye'.
 
R

Robert Klemme

Josh Cheek wrote in post #983791:

Uh oh. =A0Someone is going to have to explain that to me. =A0$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

--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 
7

7stud --

Robert Klemme wrote in post #984624:
$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}"
#puts "name = #{name}" #error: undefined variable or method 'name'

return procs
end


arr = test

arr.each do |a_proc|
a_proc.call
end

--output:--
$1 = c
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. 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.
 
R

Robert Klemme

Robert Klemme wrote in post #984624:

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. =A0Yet a new name variable is created
every time through the each loop, and each proc closes over a different
name variable. =A0So 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". :)

Cheers

robert

--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 
7

7stud --

$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 occurred inside the each block?

Thanks.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,770
Messages
2,569,584
Members
45,078
Latest member
MakersCBDBlood

Latest Threads

Top