dynamically defining a method on an instance that references vars in enclosing scope?

  • Thread starter Christopher J. Bottaro
  • Start date
C

Christopher J. Bottaro

Hello. I want to do this.

instance = MyClass.new
x = 1
def instance.foo
puts x
end
undefined local variable or method `x' for #<MyClass:0x31ca8c4>

How do get rid of that error?

Thanks for the help.
 
D

David A. Black

Hi --

Hello. I want to do this.

instance = MyClass.new
x = 1
def instance.foo
puts x
end
undefined local variable or method `x' for #<MyClass:0x31ca8c4>

How do get rid of that error?

You can use define_method, which takes a block and therefore can use
the local variables in scope at the time it's called. You'd have to do
something equivalent to the familiar:

class Object
def singleton_class
class << self
self
end
end
end

(Aside to Matz: can we *please* have that in 1.9/2.0? :)

and then:

instance.singleton_class.class_eval {
define_method("foo") { puts x }
}


David
 
M

mutahhir hayat

The problem is of scope. foo is a method belonging to the 'instance'
variable. 'x' is defined in global scope, but since its not $x its not
a global variable. When you start using x inside the method, the
interpreter doesn't recognize a 'x' variable defined, hence the error.
Either, define x inside MyClass, or mark x as a global variable using
$x, which would make...

....
$x = 1
def instance.foo
puts $x
end

to: Christopher
I'd start off with a good introduction to scopes, that'll clear this
scenario out. Plus buy the pickaxe book. Great read for beginners.
 
D

David A. Black

Hi --

The problem is of scope. foo is a method belonging to the 'instance'
variable. 'x' is defined in global scope, but since its not $x its not
a global variable. When you start using x inside the method, the
interpreter doesn't recognize a 'x' variable defined, hence the error.
Either, define x inside MyClass, or mark x as a global variable using
$x, which would make...

....
$x = 1
def instance.foo
puts $x
end

I know that the problem is scope. That's why I suggested a way to
manipulate the scope :)

I definitely would not (and deliberately did not) throw a global
variable at it. Also, defining x inside MyClass doesn't help, because
that's also a different local scope.


David
 
M

mutahhir hayat

I know that the problem is scope. That's why I suggested a way to
manipulate the scope :)

I definitely would not (and deliberately did not) throw a global
variable at it. Also, defining x inside MyClass doesn't help, because
that's also a different local scope.

I wasn't sure, but running an intermix of your code with the original
still would give the same error in irb. I wasn't advocating the use of
globals, ( though, come to think about it, i do feel bad now even
mentioning it :p) but I didn't consider that x wouldn't be usable.

:) I'm still getting used to this metaprogramming thing, but how did
you change scope? In my view, the scope remains exactly same, you just
allowed the method to be inserted into the object instance
dynamically.

Anyways, simplest i found was: (swearing never to mention global again... :p)

class MyClass
attr_accessor :x
#... or if you want you could do
# def initialize(x); @x = x; end
end

instance = MyClass.new

def instance.foo
puts x
end

instance.x = 1
instance.foo


would work.

MT.
 
D

David A. Black

Hi --

I wasn't sure, but running an intermix of your code with the original
still would give the same error in irb. I wasn't advocating the use of
globals, ( though, come to think about it, i do feel bad now even
mentioning it :p) but I didn't consider that x wouldn't be usable.

I don't see the error:

irb(main):001:0> class Object
irb(main):002:1> def singleton_class
irb(main):003:2> class << self
irb(main):004:3> self
irb(main):005:3> end
irb(main):006:2> end
irb(main):007:1> end
=> nil
irb(main):008:0>
irb(main):009:0* instance = Object.new
=> #<Object:0x64f50>
irb(main):010:0> x = 1
=> 1
irb(main):011:0>
irb(main):012:0* instance.singleton_class.class_eval {
irb(main):013:1* define_method("foo") { puts x }
irb(main):014:1> }
=> #<Proc:0x00058728@(irb):13>
irb(main):015:0>
irb(main):016:0* instance.foo
1
:) I'm still getting used to this metaprogramming thing, but how did
you change scope? In my view, the scope remains exactly same, you just
allowed the method to be inserted into the object instance
dynamically.

That's the thing: I didn't change scope. The block that uses x (line
013 above) is in the same scope as the line that defines x (010) -- at
least, in the same scope block-style. (Meaning: the block can see all
the locals that are already in scope.)
Anyways, simplest i found was: (swearing never to mention global again... :p)

class MyClass
attr_accessor :x
#... or if you want you could do
# def initialize(x); @x = x; end
end

instance = MyClass.new

def instance.foo
puts x
end

instance.x = 1
instance.foo


would work.

Yes, but you've shifted to instance variables. That's a more common
use case than the class_eval/define_method one, but they're both worth
knowing about.


David
 
M

mutahhir hayat

Hi --



I don't see the error:

Sorry, my bad, i rewrote it in my irb, and it validates your code. :)
Must be getting to bed now, clock's slowing.
irb(main):001:0> class Object
irb(main):002:1> def singleton_class
irb(main):003:2> class << self
irb(main):004:3> self
irb(main):005:3> end
irb(main):006:2> end
irb(main):007:1> end
=> nil
irb(main):008:0>
irb(main):009:0* instance = Object.new
=> #<Object:0x64f50>
irb(main):010:0> x = 1
=> 1
irb(main):011:0>
irb(main):012:0* instance.singleton_class.class_eval {
irb(main):013:1* define_method("foo") { puts x }
irb(main):014:1> }
=> #<Proc:0x00058728@(irb):13>
irb(main):015:0>
irb(main):016:0* instance.foo

1


That's the thing: I didn't change scope. The block that uses x (line
013 above) is in the same scope as the line that defines x (010) -- at
least, in the same scope block-style. (Meaning: the block can see all
the locals that are already in scope.)

I get you, since the block passed to class_eval was defined in the
same scope as x, it would be knowledgeable about it. But tell me,
other than academic, won't this be really bad to use? Like, maybe you
shadow some variable, and so on... I dunno, :p as i said, nearing my
limit, so maybe i'm just jabbering here. But it was fun learning from
you, glad to be on this list. Thanks.

( Although, according to my judgment (sorry if i'm wrong) of the level
of the programmer by the nature of the question, i'm pretty sure we
made the questioner scream ) :)

See ya,
MT
 
D

David A. Black

Hi --

I get you, since the block passed to class_eval was defined in the
same scope as x, it would be knowledgeable about it. But tell me,
other than academic, won't this be really bad to use? Like, maybe you
shadow some variable, and so on... I dunno, :p as i said, nearing my
limit, so maybe i'm just jabbering here. But it was fun learning from
you, glad to be on this list. Thanks.

One of the most important roles of code blocks is to serve as closures
-- that is, functions that remember the context of their own creation.
In Ruby 1.9, the rules of parameter assignment are changing somewhat,
so that it's easier to avoid trampling outside variables; but you can
still use those variables if that's your purpose.


David
 
C

Christopher J. Bottaro

One of the most important roles of code blocks is to serve as closures
That is what I was going for. Thank you for your solution!
You'd have to do something equivalent to the familiar:
class Object
def singleton_class
class << self
self
end
end
end

Though I more or less understand what that is doing, it's not
"familiar" to me. Can you tell me name of that idiom or point me in
the direction of where it is discussed?

I do understand that if I simply do something like

my_instance.class.define_method :name do
# blah blah
end

Then 'name' will be defined for all instances of the class and not
just for the few instances that I want.

Thanks again.


<off_topic_rant>
I wish Google Groups would send email notification to threads I
participate in by default. So many times I forget that I even posted
until months later.
</off_topic_rant>
 

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

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,483
Members
44,901
Latest member
Noble71S45

Latest Threads

Top