instance_eval to evaluate methods inside a block in context

A

Adam Anderson

http://pastie.caboo.se/114839

Can anyone tell me why this wouldn't/shouldn't/should work? I don't
want to pull out all of my hair trying to figure this out if it isn't
even possible.


Thanks,
Adam


class Calculator
def evaluate(&script)
self.instance_eval(&script)
end

def two
"2"
end
end

class Foo
def hi
puts "hi"
end

def howdy
Calculator.new.evaluate do
puts two
hi
end
end
end

Foo.new.howdy

Attachments:
http://www.ruby-forum.com/attachment/920/w.rb
 
G

Gary Wright

Can anyone tell me why this wouldn't/shouldn't/should work? I don't
want to pull out all of my hair trying to figure this out if it isn't
even possible.

When you call instance_eval in Calculator#evaluate, all method calls
without an explicit receiver are called relative to the calculator
instance. The call to two() works because two() is defined as an
instance method of Calculator. The call to hi() fails because hi()
is an instance method of Foo not of Calculator.

It is important to realize that method invocation with an instance_eval
block is resolved dynamically based on self and not lexically based
on where the block is declared (in this case within an instance method
of Foo). By calling instance_eval you are explicitly overriding the
normal binding of self within the block.

Try changing howdy() as follows:
def howdy
Calculator.new.evaluate do
puts two
puts self.inspect # <== find out what self is
hi
end

You'll see that self is an instance of calculator and hi() isn't
defined for for Calculator objects, only for Foo objects.

Gary Wright
 
P

Phrogz

http://pastie.caboo.se/114839

Can anyone tell me why this wouldn't/shouldn't/should work? I don't
want to pull out all of my hair trying to figure this out if it isn't
even possible.

You can either do this:
class Calculator
def evaluate(&script)
self.instance_eval(&script)
end
def two
"2"
end
end

class Foo
def hi
puts "hi"
end
def howdy
me = self
Calculator.new.evaluate do
puts two
me.hi
end
end
end

Foo.new.howdy


....or this...

class Calculator
def evaluate(&script)
yield self
end
def two
"2"
end
end

class Foo
def hi
puts "hi"
end
def howdy
Calculator.new.evaluate do |cal|
puts cal.two
hi
end
end
end

Foo.new.howdy
 
A

Adam Anderson

So I guess I understand now why this can't be done, but I want to do
something like this. Basically I wish that I could call a block and
every method inside that block would be called against the instance of
the class in which the block is being evaluated and if it gets a
NameError or method_missing or whatever then it moves outside the scope
of that block.

From what I am reading it would seem that this is just not a
possibility, however.

Thanks for your help!
 
G

Gary Wright

From what I am reading it would seem that this is just not a
possibility, however.

You could use method_missing in the implicit object to delegate
the message to another object:

class A
def foo
warn "foo in #{self.inspect}"
end
def backup=(b); @backup = b; end
def method_missing(*args, &block)
if @backup
@backup.send(*args, &block)
else
super
end
end
end


class B
def bar
warn "bar in #{self.inspect}"
end
def with(x, &b)
x.backup = self
x.instance_eval &b
end
end

a = A.new
b = B.new.with(a) {
foo # foo in instance of A
bar # bar in instance of B
}
 
A

Adam Anderson

This isn't really what I'm trying to do, however. Your example would
mean that I would have to pass a backup to a block everytime. What I
want to do is basically impossible unless I can change scoping rules, I
think.

my_method do
another_instance_method("foo")
method_outside_of_instance("bar")
end

which would just be equivalent to

my_method do |lol|
lol.another_instance_method("foo")
method_outside_of_instance("bar")
end

where the lol object is just an instance of the class which has defined
both my_method and another_instance_method.

I want any method to implicitly check the scope of the class in which
the method is being called and *THEN* check the scope outside the class.
This is just an exercise in making code more readable and require less
typing, as far I am concerned.

If I use instance_eval then all the methods inside then only those
instance methods will work. I am pretty much resigned to believing this
is impossible. I'm always taking suggestions, though!


Thanks,
Adam
 
G

Gary Wright

I want any method to implicitly check the scope of the class in which
the method is being called and *THEN* check the scope outside the
class.
This is just an exercise in making code more readable and require less
typing, as far I am concerned.

I think what you are suggesting is the ability to queue up a number
of objects to be used as 'self' while a block is being evaluated. If
none of the objects can handle a message, then the process would
be repeated this time looking for method_missing().

Something like:

also_eval(a,b,c) { #code }

So the code would be executed with the 'normal' self. If a method
was not found it would be searched for via object a then b then c.

The main problem with implementing this is that you really want to
insert a step between the normal method lookup and method_missing and
I don't think that can be done without using method_missing itself.

Anyway, here is a solution that uses method_missing to link the
objects. The last object in the chain is responsible for doing
something via its own method_missing. This solution will not
properly handle nested blocks due the limitations on define_method().
I'm sure this could be improved. I'm not sure how useful this is
but it was a fun problem to think through.

module Kernel
def also_eval(*args, &block)
block_self = eval "self", block.binding
args.inject(block_self) { |op1, op2|
(class <<op1; self; end).send:)define_method, :method_missing)
{ |*args|
begin
op2.send(*args)
rescue NoMethodError
begin
super
rescue NoMethodError
op2.send:)method_missing, *args)
end
end
}
op2
}
block.call
end
end

class A
also_eval({:a=>1}, [100,200]) {
p self # A
p keys # the hash
p first # the array
p bogus # not found
}
end
 

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,969
Messages
2,570,161
Members
46,705
Latest member
Stefkari24

Latest Threads

Top