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

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.

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? :slight_smile:

and then:

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

David

···

On Sat, 22 Mar 2008, Christopher J. Bottaro wrote:

--
Upcoming Rails training from David A. Black and Ruby Power and Light:
   ADVANCING WITH RAILS, April 14-17 2008, New York City
   CORE RAILS, June 24-27 2008, London (Skills Matter)
See http://www.rubypal.com for details. Berlin dates coming soon!

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'd start off with a good introduction to scopes, that'll clear this
scenario out. Plus buy the pickaxe book. Great read for beginners.

···

to: Christopher

On Sat, Mar 22, 2008 at 12:32 AM, David A. Black <dblack@rubypal.com> wrote:

Hi --

On Sat, 22 Mar 2008, Christopher J. Bottaro wrote:

> 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? :slight_smile:

and then:

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

David

--
Upcoming Rails training from David A. Black and Ruby Power and Light:
   ADVANCING WITH RAILS, April 14-17 2008, New York City
   CORE RAILS, June 24-27 2008, London (Skills Matter)
See http://www.rubypal.com for details. Berlin dates coming soon!

Hi --

···

On Sat, 22 Mar 2008, mutahhir hayat wrote:

On Sat, Mar 22, 2008 at 12:32 AM, David A. Black <dblack@rubypal.com> wrote:

Hi --

On Sat, 22 Mar 2008, Christopher J. Bottaro wrote:

> 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? :slight_smile:

and then:

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

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 :slight_smile:

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

--
Upcoming Rails training from David A. Black and Ruby Power and Light:
   ADVANCING WITH RAILS, April 14-17 2008, New York City
   CORE RAILS, June 24-27 2008, London (Skills Matter)
See http://www.rubypal.com for details. Berlin dates coming soon!

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

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.

:slight_smile: 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.

···

David

Hi --

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

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

:slight_smile: 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

···

On Sat, 22 Mar 2008, mutahhir hayat wrote:

--
Upcoming Rails training from David A. Black and Ruby Power and Light:
   ADVANCING WITH RAILS, April 14-17 2008, New York City
   CORE RAILS, June 24-27 2008, London (Skills Matter)
See http://www.rubypal.com for details. Berlin dates coming soon!

Hi --

>> I know that the problem is scope. That's why I suggested a way to
>> manipulate the scope :slight_smile:
>>
>> 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 don't see the error:

Sorry, my bad, i rewrote it in my irb, and it validates your code. :slight_smile:
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

> :slight_smile: 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.)

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, :stuck_out_tongue: 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 ) :slight_smile:

See ya,
MT

···

On Sat, Mar 22, 2008 at 1:37 AM, David A. Black <dblack@rubypal.com> wrote:

On Sat, 22 Mar 2008, mutahhir hayat wrote:

> 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

--
Upcoming Rails training from David A. Black and Ruby Power and Light:
   ADVANCING WITH RAILS, April 14-17 2008, New York City
   CORE RAILS, June 24-27 2008, London (Skills Matter)
See http://www.rubypal.com for details. Berlin dates coming soon!

Hi --

···

On Sat, 22 Mar 2008, mutahhir hayat wrote:

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, :stuck_out_tongue: 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

--
Upcoming Rails training from David A. Black and Ruby Power and Light:
   ADVANCING WITH RAILS, April 14-17 2008, New York City
   CORE RAILS, June 24-27 2008, London (Skills Matter)
See http://www.rubypal.com for details. Berlin dates coming soon!

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>

···

On Mar 21, 1:26 pm, "David A. Black" <dbl...@rubypal.com> wrote: