eval, module_eval, define_method, and all that

G

Greg Weeks

I don't know how Ruby scoping works when the code involves:

nested "def"s
eval
module_eval
instance_eval
define_method

It would be nice to find an app-note for this issue. (More generally,
it would be nice to see written down how 'self' and 'klass' are used in
the Ruby execution model.)

It would also be nice to see a history of these meta-programming
constructs. (I say "meta-programming", since we're talking about code
that executes code.) I wouldn't be surprised if once upon a time Ruby
had only nested "def"s and "eval"s. However, I don't know how to write
my own version of, say, "attr_reader" using just those two. For
example, this doesn't work:

def my_attr_reader name
eval <<-STOP
def #{name}
@#{name}
end
STOP
end

I wonder if there is a way. Moving on, the following all work:

def my_attr_reader name
eval <<-STOP
define_method :#{name} do
p C # IGNORE FOR NOW
@#{name}
end
STOP
end

def my_attr_reader name
module_eval <<-STOP
def #{name}
p C # IGNORE FOR NOW
@#{name}
end
STOP
end

def my_attr_reader name
module_eval <<-STOP
define_method :#{name} do
p C # IGNORE FOR NOW
@#{name}
end
STOP
end

def my_attr_reader name # MY FAVORITE, FYI
define_method name do
p C # IGNORE FOR NOW
instance_variable_get "@"+name.to_s
end
end

Alas, There's More Than One Way To Do It. Now let's exercise those
"p C" statements:

module M
C = "M"
# INSERT ONE OF THE my_attr_reader DEFINITIONS HERE
end
class X
C = "X"
class <<X ; include M ; end
attr_writer :a
my_attr_reader :a
end
x = X.new
x.a = 4
x.a -> prints ???

With the four examples above, the printed value of C is:

"M" "X" "X" "M"

My head is swimming. Ideally, I'll find my app-note, which will clear
my mind as marvelously as "How Classes and Objects Interact" did in the
Pickaxe book.

PS: "@"+name.to_s is gross. Why doesn't "instance_variable_get" accept
an *implicit* "@"?
 
G

Greg Weeks

I wrote above that my head was swimming. Not enough, evidentally:

If I want my_attr_reader to handle multiple attributes, this works:

def my_attr_reader *names
names.each do |name| # THIS IS THE ONLY LINE THAT DIFFERS
define_method name do
instance_variable_get "@"+name.to_s
end
end
end

But this doesn't:

def my_attr_reader *names
for name in names # THIS IS THE ONLY LINE THAT DIFFERS
define_method name do
instance_variable_get "@"+name.to_s
end
end
end

Ouch.
 
G

Greg Weeks

My interest (obsession?) with methods defining methods partly derives
from the fact Ruby got a huge boost (via Rails) because of it.
Unfortunately, Ruby requires *three* special behaviors to make it work,
and these behaviors are provided nonorthogonally. The following fails
for three reasons. (Please forgive how silly it looks at this point.)

class A
class <<A
def my_attr_reader var # 'var' will be bound to :foo.
def var
"@"+var.to_s
end
end
end
my_attr_reader :foo
attr_writer :foo
end

1. In "def foo ; @foo ; end", "foo" and "@foo" are syntax literals.
Neither can be replaced with an expression.

2. The inner "def" is installed in <<A rather than in A.

3. Ruby tends to scope lexically rather than dynamically. "@foo" is
likely to be scoped as an instance variable of the A class rather than
of A instances. Similarly, a referenced constant is likely to be looked
up in <<A rather than A.

Problem #1 can be solved either by evaluating strings or by using
"define_method", "instance_variable_get", and such.

Problem #2 can be solved using "module_eval" and/or "define_method".

Problem #3 is solved (I think) by a combination of "module_eval" and
string evaluation.

"module_eval" with a string argument seems like the best technique from
an ease-of-use point of view. But string evaluation rubs some people
the wrong way. On the other hand, "module_eval" (or "define_method")
with a block argument scopes constants lexically and instance variables
dynamically:

CONST = "Object"

$block = proc do
def get_stuff
[@foo, CONST, self]
end
end

class A
CONST = "A"
class <<A
CONST = "<<A"
def def_get_stuff
module_eval &$block
end
end
attr_accessor :foo
def_get_stuff
end

a = A.new
a.foo = 22
p a.get_stuff -> [22, "Object", #<A:0x401c2448 @foo=22>]

I don't know if this result is intrinsically messy or if I'm looking at
it from the wrong angle.
 
G

Greg Weeks

In this forum, I've mentioned several times that some people don't like
"eval". Here is the end of section 11.3.5 from "The Ruby Way":

In previous versions of Ruby, we often defined methods at runtime by
calling "eval". In essence, "define_method" can and should be used in
all these circumstances.

"ALL these circumstances". That's strong stuff. Well, I'm ready to bow
to authority. But how can I see "define_method" as loveable? How about
this: "define_method" is Minimally Magical.

Here's what I mean. Given the name "define_method", we naturally
expect "foo" and "bar" below to have the same behavior:

CONST = "Object"

class A
CONST = "A"
@val = "A"
attr_accessor :val
def foo
[CONST, @val, self]
end
define_method :bar do
[CONST, @val, self]
end
end

a = A.new
a.val = "a"
p a.foo -> ["A", "a", #<A:0x401c24fc @val="a">]
p a.bar -> ["A", "a", #<A:0x401c24fc @val="a">]

If "define_method" was not magical, then, within its block argument, the
values of CONST and @val and self would be "A" and "A" and A (the class
object). The minimal magic required to get the desired answer is to
make @val and self refer to the A instance, but leave CONST to scope
lexically. And that seems to be what "define_method" does.

I personally will follow "The Ruby Way" (second edition). Stand behind
me, *eval methods!
 

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

Forum statistics

Threads
473,968
Messages
2,570,152
Members
46,698
Latest member
LydiaHalle

Latest Threads

Top