'def', but with a closure

A

Albert Schlef

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

Hidetoshi NAGAI

From: Albert Schlef <[email protected]>
Subject: 'def', but with a closure
Date: Mon, 15 Feb 2010 14:10:36 +0900
Message-ID: said:
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.
------------------------------------------------------
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]}
 
J

Josh Cheek

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

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

Hidetoshi NAGAI

From: Josh Cheek <[email protected]>
Subject: Re: 'def', but with a closure
Date: Mon, 15 Feb 2010 15:35:44 +0900
Message-ID: said:
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]}
 
R

Robert Klemme

2010/2/15 Albert Schlef said:
It occurred to me several times that I wanted to do:

[email protected] do |owner, model|
=A0 =A0widget =3D TkLabel.new:)text =3D> model)
=A0 =A0def widget.get_owner
=A0 =A0 =A0return owner
=A0 =A0end
=A0end

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:

[email protected] do |owner, model|
=A0 =A0widget =3D TkLabel.new:)text =3D> model)
=A0 =A0meta =3D (class << widget; self; end)
=A0 =A0meta.send:)define_method, :get_owner) do
=A0 =A0 =A0return owner
=A0 =A0end
=A0end

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 =3D TkLabel.new:)text =3D> 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

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

Brian Candler

Robert said:
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
 
A

Albert Schlef

Hidetoshi said:
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.
 
A

Albert Schlef

Robert said:
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.
 
A

Albert Schlef

Brian said:
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.
 
B

Brian Candler

Albert said:
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.
 
B

Brian Candler

Brian said:
"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.
 

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,982
Messages
2,570,190
Members
46,736
Latest member
zacharyharris

Latest Threads

Top