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

From: Albert S. [email protected]
Subject: ‘def’, but with a closure
Date: Mon, 15 Feb 2010 14:10:36 +0900
Message-ID: [email protected]

see the enveloping variables, so the ‘owner’ within the ‘def’ isn’t
recognized.

e.g.

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

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.

From: Josh C. [email protected]
Subject: Re: ‘def’, but with a closure
Date: Mon, 15 Feb 2010 15:35:44 +0900
Message-ID:
[email protected]

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.

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

2010/2/15 Albert S. [email protected]:

see the enveloping variables, so the ‘owner’ within the ‘def’ isn’t
end

It work. Yet, it looks a bit ugly. Is there any “nicer” way I’m missing?

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

Robert K. 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

Robert K. 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.

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.

Albert S. 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 ’ where is dynamic, you
have to use eval.

Brian C. 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.

Brian C. 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.

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