'def', but with a closure

It occurred to me several times that I wanted to do:

  @cars.each do |owner, model|
    widget = TkLabel.new(:text => model)
    def widget.get_owner
      return owner
    end
  end

But, of course, this doesn't work because the code inside 'def' doesn't
see the enveloping variables, so the 'owner' within the 'def' isn't
recognized.

So instead I do:

  @cars.each do |owner, model|
    widget = TkLabel.new(:text => model)
    meta = (class << widget; self; end)
    meta.send(:define_method, :get_owner) do
      return owner
    end
  end

It work. Yet, it looks a bit ugly. Is there any "nicer" way I'm missing?

···

--
Posted via http://www.ruby-forum.com/.

Message-ID: <d6e7c0160098036697ba45ba9e00c511@ruby-forum.com>

It occurred to me several times that I wanted to do:

  @cars.each do |owner, model|
    widget = TkLabel.new(:text => model)
    def widget.get_owner
      return owner
    end
  end

But, of course, this doesn't work because the code inside 'def' doesn't
see the enveloping variables, so the 'owner' within the 'def' isn't
recognized.

e.g.

···

From: Albert Schlef <albertschlef@gmail.com>
Subject: 'def', but with a closure
Date: Mon, 15 Feb 2010 14:10:36 +0900
------------------------------------------------------
labels = @cars.collect{|owner, model|
  TkLabel.new(:text=>model){ # in this block, self is the created widget.
    @owner = owner
    def get_owner
      @owner
    end
  }
}

labels.each{|w| p [w.text, w.get_owner]}
------------------------------------------------------
--
Hidetoshi NAGAI (nagai@ai.kyutech.ac.jp)

You can at least shorten it a bit

@cars.each do |owner, model|
   widget = TkLabel.new(:text => model)
   (class << widget; self; end).send(:define_method, :get_owner) do
     owner
   end
end

I find this better than other approaches because storing in an
instance variable is not the same as using the closure. Josh's
approach with eval has the disadvantage of a) using eval in the first
place, b) it does not work for all types of objects handed in as owner
and c) for Strings it's less efficient because it will create a new
String instance every time the method is invoked.

Kind regards

robert

···

2010/2/15 Albert Schlef <albertschlef@gmail.com>:

It occurred to me several times that I wanted to do:

@cars.each do |owner, model|
widget = TkLabel.new(:text => model)
def widget.get_owner
return owner
end
end

But, of course, this doesn't work because the code inside 'def' doesn't
see the enveloping variables, so the 'owner' within the 'def' isn't
recognized.

So instead I do:

@cars.each do |owner, model|
widget = TkLabel.new(:text => model)
meta = (class << widget; self; end)
meta.send(:define_method, :get_owner) do
return owner
end
end

It work. Yet, it looks a bit ugly. Is there any "nicer" way I'm missing?

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

Is there some reason why Ruby hasn't made it possible to just do

@cars.each do |owner, model|
  widget = TkLabel.new( :text => model )
  widget.define_method( :get_owner ) { owner }
end

I find myself conceptually knowing what I want to do, but having to jump
through lots of hoops to do it, or telling myself that I'm not supposed to
do

eval <<-END_OF_METHOD
  def widget.get_owner
    #{owner.inspect}
  end
END_OF_METHOD

But if I'm supposed to avoid things like that, then why make them so much
easier than the alternative? Eval worked first time I tried (granted my test
case had owner as a string), but I spent maybe 30 min trying to get Albert's
way to work. Hidetoshi's solution works because of TkLabel's behaviour, but
can't be expected to work for any object (ie not generalizable).

Is it supposed to be an act of discouragement, to prevent us from doing
something dangerous?
Was it just not considered that most people would want or need to do
something like this?
Is it something that is actually very difficult to implement / not possible,
due to a deeper Ruby model than I understand?
Is it actually really easy, and I just am slow to catch on?

Any thoughts?

Capability like this gets me excited, but the difficulty of implementing it
makes me frustrated.

Robert Klemme wrote:

You can at least shorten it a bit

@cars.each do |owner, model|
   widget = TkLabel.new(:text => model)
   (class << widget; self; end).send(:define_method, :get_owner) do
     owner
   end
end

You can use a block (non-string) eval, which isn't quite as short but
avoids the 'send'. It's nicer if you have a whole bunch of define_method
calls.

  @cars.each do |owner, model|
    widget = TkLabel.new(:text => model)
    (class << widget; self; end).class_eval do
      define_method(:get_owner) do
        owner
      end
    end
  end

Or use an explicit instance variable instead of a closure.

  module Owner
    def get_owner
      @owner
    end
  end

  @cars = {"jim"=>"KA", "fred"=>"Porsche", "trunky"=>"Mini"}
  @cars.each do |owner, model|
    widget = TkLabel.new(:text => model)
    widget.extend Owner
    widget.instance_variable_set(:@owner, owner)
    # or: widget.instance_eval { @owner = owner }
    # or: widget.set_owner(owner)
  end

···

--
Posted via http://www.ruby-forum.com/\.

Robert Klemme wrote:

Josh's approach with eval has the disadvantage of [...]

It has another disadvantage: It's possible to imagine a Ruby
implementation that doesn't have a compiler at run-time.

It's surprising to see that a few "important" libraries are using eval.
ActiveRecord, for example. I wonder why.

···

--
Posted via http://www.ruby-forum.com/\.

Message-ID: <4a2cfc231002142235k39a6aa2coaf956300dc3555d7@mail.gmail.com>

way to work. Hidetoshi's solution works because of TkLabel's behaviour, but
can't be expected to work for any object (ie not generalizable).

e.g.

···

From: Josh Cheek <josh.cheek@gmail.com>
Subject: Re: 'def', but with a closure
Date: Mon, 15 Feb 2010 15:35:44 +0900
------------------------------------------------------
objs = @cars.collect{|owner, model|
  obj = Object.new
  obj.instance_eval{
    @owner = owner
    @model = model
    def get_model
      @model
    end
    def get_owner
      @owner
    end
  }
  obj
}

objs.each{|o| p [o.get_model, o.get_owner]}
------------------------------------------------------
--
Hidetoshi NAGAI (nagai@ai.kyutech.ac.jp)

Brian Candler wrote:

You can use a block (non-string) eval, which isn't quite as short but
avoids the 'send'. It's nicer if you have a whole bunch of define_method
calls.

  @cars.each do |owner, model|
    widget = TkLabel.new(:text => model)
    (class << widget; self; end).class_eval do
      define_method(:get_owner) do
        owner
      end
    end
  end

Thanks. I tried to shorten it to:

   @cars.each do |owner, model|
     widget = TkLabel.new(:text => model)
     class << widget
       define_method(:get_owner) do
         owner
       end
     end
   end

But it turns out that 'class', just like 'def', cuts off the inner code
from the variables outside.

···

--
Posted via http://www.ruby-forum.com/\.

Hidetoshi NAGAI wrote:

objs = @cars.collect{|owner, model|
  obj = Object.new
  obj.instance_eval{
    @owner = owner
    @model = model
    def get_model
      @model
    end
    def get_owner
      @owner
    end
  }
  obj
}

Thanks. That's useful.

I wouldn't have imagined `def` inside instance_eval adds the method to
the singleton.

BTW, anybody knows what exactly `def` does? Until now I thought it does
this:

   Lookup the enclosing class (or module) and add the method
   to its m_tbl.

Based on Hidetoshi's code it seems `def` does this:

   Look at 'self'. If it's a class (or a module), add the method
   to its m_tbl. If it isn't, get its singleton and add the method
   to *its* m_tbl.

···

--
Posted via http://www.ruby-forum.com/\.

Albert Schlef wrote:

BTW, anybody knows what exactly `def` does? Until now I thought it does
this:

   Lookup the enclosing class (or module) and add the method
   to its m_tbl.

Based on Hidetoshi's code it seems `def` does this:

   Look at 'self'. If it's a class (or a module), add the method
   to its m_tbl. If it isn't, get its singleton and add the method
   to *its* m_tbl.

I believe it's slightly more complex than that.

In executing code, you're familiar with the idea of "the current
object". This is made available as 'self'.

However there's also a more hidden concept of "the current class". It's
rather difficult to get hold of this, but it's where def defines
instance methods.

"The current class" is set inside a class ... end construct. But as
you've found, this starts a new scope. So you can use class_eval
instead, which sets both the current object and the current class.

Compare:

class Foo; end
Foo.instance_eval do
  def foo; puts "XXX!"; end
end
Foo.foo # you have made a class method

class Bar; end
Bar.class_eval do
  def bar; puts "YYY!"; end
end
Bar.new.bar # you have made an instance method

Anyway, this is moot if you are working with closures, because 'def'
also starts a fresh scope.

It's surprising to see that a few "important" libraries are using eval.
ActiveRecord, for example. I wonder why.

'def' methods are more efficient at runtime (and also less liable to
unforeseen side-effects), precisely because they do not have access to
outer scopes. If you want to 'def <foo>' where <foo> is dynamic, you
have to use eval.

···

--
Posted via http://www.ruby-forum.com/\.

Brian Candler wrote:

"The current class" is set inside a class ... end construct. But as
you've found, this starts a new scope. So you can use class_eval
instead, which sets both the current object and the current class.

Note that instance_eval sets the current class to the object's singleton
class, whereas class_eval sets the current class to the object itself,
if that object is a Class.

So this works:

@cars.each do |owner, model|
  widget = TkLabel.new(:text => model)
  widget.instance_eval do
    def get_owner
      "nobody"
    end
  end
end

Unfortunately, define_method is a method of Module, not Object. So this
doesn't work:

@cars.each do |owner, model|
  widget = TkLabel.new(:text => model)
  widget.instance_eval do
    define_method(:get_owner) do
      owner
    end
  end
end

Hence the need for (class << widget; self; end).class_eval

Regards,

Brian.

···

--
Posted via http://www.ruby-forum.com/\.