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
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.
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.
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
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
}
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
Gary W. wrote:
On Nov 7, 2007, at 3:58 AM, Adam A. wrote:
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
}
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
This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.