Is it possible to have Methods inside Methods?
UPDATE: Since this answer seems to have gotten some interest lately, I wanted to point out that there is discussion on the Ruby issue tracker to remove the feature discussed here, namely to forbid having method definitions inside a method body.
No, Ruby doesn't have nested methods.
You can do something like this:
class Test1
def meth1
def meth2
puts "Yay"
end
meth2
end
end
Test1.new.meth1
But that is not a nested method. I repeat: Ruby does not have nested methods.
What this is, is a dynamic method definition. When you run meth1
, the body of meth1
will be executed. The body just happens to define a method named meth2
, which is why after running meth1
once, you can call meth2
.
But where is meth2
defined? Well, it's obviously not defined as a nested method, since there are no nested methods in Ruby. It's defined as an instance method of Test1
:
Test1.new.meth2
# Yay
Also, it will obviously be redefined every time you run meth1
:
Test1.new.meth1
# Yay
Test1.new.meth1
# test1.rb:3: warning: method redefined; discarding old meth2
# test1.rb:3: warning: previous definition of meth2 was here
# Yay
In short: no, Ruby does not support nested methods.
Note also that in Ruby, method bodies cannot be closures, only block bodies can. This pretty much eliminates the major use case for nested methods, since even if Ruby supported nested methods, you couldn't use the outer method's variables in the nested method.
UPDATE CONTINUED: at a later stage, then, this syntax might be re-used for adding nested methods to Ruby, which would behave the way I described: they would be scoped to their containing method, i.e. invisible and inaccessible outside of their containing method body. And possibly, they would have access to their containing method's lexical scope. However, if you read the discussion I linked above, you can observe that matz is heavily against nested methods (but still for removing nested method definitions).
Actually it's possible. You can use procs/lambda for this.
def test(value)
inner = ->() {
value * value
}
inner.call()
end
No, no, Ruby does have nested methods. Check this:
def outer_method(arg)
outer_variable = "y"
inner_method = lambda {
puts arg
puts outer_variable
}
inner_method[]
end
outer_method "x" # prints "x", "y"
You can do something like this
module Methods
define_method :outer do
outer_var = 1
define_method :inner do
puts "defining inner"
inner_var = outer_var +1
end
outer_var
end
extend self
end
Methods.outer
#=> defining inner
#=> 1
Methods.inner
#=> 2
This is useful when you're doing things like writing DSLs which require sharing of scope between methods. But otherwise, you're much better off doing anything else, because as the other answers said, inner
is redefined whenever outer
is invoked. If you want this behavior, and you sometimes might, this is a good way to get it.
The Ruby way is to fake it with confusing hacks that will have some users wondering "How in the fuck does this even work?", while the less curious will simply memorize the syntax needed to use the thing. If you've ever used Rake or Rails, you've seen this kind of thing.
Here is such a hack:
def mlet(name,func)
my_class = (Class.new do
def initialize(name,func)
@name=name
@func=func
end
def method_missing(methname, *args)
puts "method_missing called on #{methname}"
if methname == @name
puts "Calling function #{@func}"
@func.call(*args)
else
raise NoMethodError.new "Undefined method `#{methname}' in mlet"
end
end
end)
yield my_class.new(name,func)
end
What that does is define a top-level method that creates a class and passes it to a block. The class uses method_missing
to pretend that it has a method with the name you chose. It "implements" the method by calling the lambda you must provide. By naming the object with a one-letter name, you can minimize the amount of extra typing that it requires (which is the same thing that Rails does in its schema.rb
). mlet
is named after the Common Lisp form flet
, except where f
stands for "function", m
stands for "method".
You use it like this:
def outer
mlet :inner, ->(x) { x*2 } do |c|
c.inner 12
end
end
It is possible to make a similar contraption that allows for multiple inner functions to be defined without additional nesting, but that requires an even uglier hack of the sort you might find in Rake's or Rspec's implementation. Figuring out how Rspec's let!
works would get you a long way towards being able to create such a horrible abomination.