nested methods and scope

J

Jamis Buck

Sorry if this has been rehashed before--I searched the archive and
didn't find anything that seemed identical to the question I've got.

Which is: if I define a method inside a method, currently the inner
method has no access to the outer method's local variables. Is this a
conscious design decision, or a bug?

def test_outer( a, b )
def test_inner( a )
puts "#{a} and #{b}"
end

test_inner( a+b )
end

test_outer( 1, 2 )

I would expect "3 and 2" to be the output, but I get an "undefined local
variable or method `b'" error from test_inner.

Can anyone shed some light on this behavior for me, either in favor of
it, or at the very least explaining it? (Yes, I know I could use a
block, instead of a nested method... but I would like to know why the
nested method approach fails.)

Thanks,

Jamis
 
J

Joel VanderWerf

Jamis said:
Sorry if this has been rehashed before--I searched the archive and
didn't find anything that seemed identical to the question I've got.

Which is: if I define a method inside a method, currently the inner
method has no access to the outer method's local variables. Is this a
conscious design decision, or a bug?

def test_outer( a, b )
def test_inner( a )
puts "#{a} and #{b}"
end

test_inner( a+b )
end

test_outer( 1, 2 )

I would expect "3 and 2" to be the output, but I get an "undefined local
variable or method `b'" error from test_inner.

Can anyone shed some light on this behavior for me, either in favor of
it, or at the very least explaining it? (Yes, I know I could use a
block, instead of a nested method... but I would like to know why the
nested method approach fails.)

I can't give much of an explanation of the rationale, but here's a way
to do what you were probably trying to do:

def test_outer( a, b )
self.class.instance_eval do
define_method :test_inner do
puts "#{a} and #{b}"
end
end

test_inner( a+b )
end

test_outer( 1, 2 ) # ==> prints "1 and 2"

The reason this works is that #define_method, unlike the def "special
form", takes a closure. You have to do the "self.class.instance_eval"
because #define_method is private.
 
J

Jamis Buck

Joel said:
I can't give much of an explanation of the rationale, but here's a way
to do what you were probably trying to do:

def test_outer( a, b )
self.class.instance_eval do
define_method :test_inner do
puts "#{a} and #{b}"
end
end

test_inner( a+b )
end

test_outer( 1, 2 ) # ==> prints "1 and 2"

The reason this works is that #define_method, unlike the def "special
form", takes a closure. You have to do the "self.class.instance_eval"
because #define_method is private.

That's clever -- thanks for the suggestion. :)

In further research, I think I've discovered why the local environment
of the declaring method aren't accessible.

def outer
def inner
puts "hello"
end
end

inner #-> displays "hello"

The inner method is actually not declared within the scope of the
(apparently) declaring method--it is promoted to exist at the same scope
level of its declarer, and therefore is not really "inside" the method
at all. I was hoping for a Pascal-like nesting of methods, which is not
really what is happening here.

I will frankly admit that there are no doubt lots of different ways to
accomplish (more-or-less) what I was wanting to do, but the benefits of
the way I original submitted (if it would work as I had hoped) are:

1) the inner method is inside the outer method's scope, and cannot be
referenced from outside. I could make the 'inner' method private and
declare it at the object scope, but then other methods of the object
could invoke it... The way I'm proposing is actually even more
restrictive than private access, and cannot be circumvented by using the
'instance_eval' trick.

2) the inner method could have complete access to the nesting methods
local environment, including other nested methods (which, incidentally,
works -- nested methods can call other nested methods in the same scope,
they just can't access local variables declared at the same scope).

This is really more of a theoretical question, though. I'm not actually
attempting to do this for any practical reason, I'm just trying to "push
the envelope" to see how much Ruby can do. :)
 

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

Similar Threads


Members online

No members online now.

Forum statistics

Threads
473,982
Messages
2,570,185
Members
46,736
Latest member
AdolphBig6

Latest Threads

Top