101

I have a method inside of a method. The interior method depends on a variable loop that is being run. Is that a bad idea?

030
  • 10,842
  • 12
  • 78
  • 123
Trip
  • 26,756
  • 46
  • 158
  • 277

7 Answers7

181

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).

nattoluvr
  • 115
  • 1
  • 7
Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • 7
    You might also show, however, how to create a closure lambda in a method for DRYness, or to run recursion. – Phrogz Feb 01 '11 at 17:30
  • 127
    I'm getting the feeling that Ruby might not have nested methods. – Mark Thomas Feb 02 '11 at 01:57
  • 17
    @Mark Thomas: Did I forget to mention that Ruby does not haved nested methods? :-) Seriously: at the time I wrote this answer, there were already three answers, every single one of which claimed that Ruby *does* have nested methods. Some of those answers even had upvotes despite being blatantly wrong. One was even accepted by the OP, again, despite being wrong. The code snippet that answer uses to prove that Ruby supports nested methods, actually proves the opposite, but apparently neither the upvoters nor the OP actually bothered to check. So, I gave one right answer for every wrong one. :-) – Jörg W Mittag Feb 02 '11 at 11:59
  • 12
    When you realize that these are all just instructions to the Kernel that modify tables and that methods and classes and modules are all just entries in tables and not really real, you become like Neo when he sees what the Matrix looks like. Then you could really become philosophical and say that besides nested methods, there are not even methods. There are not even agents. They are programs in the matrix. Even that juicy steak you are eating is just an entry in a table. – mydoghasworms Dec 03 '13 at 18:25
  • 3
    There are no methods, your code is just a simulation in The Matrix – bbozo Feb 02 '14 at 10:36
  • 2
    Note: you'll only see the "method redefined" warning if you enable ruby warnings via `-w` when invoking the interpreter, or `$-w = true` in code. – Kelvin Aug 07 '14 at 17:18
  • Great! Tell Matz I'll just copy and paste the bits of code that would've gone in an inner method, since passing the outer method's state to an external method would be even more verbose than the copied/pasted bits! – Throw Away Account May 11 '17 at 02:15
  • @ThrowawayAccount3Million: Ruby *does* have lambdas. – Jörg W Mittag May 11 '17 at 10:03
  • @JörgWMittag Nonetheless, Matz needs to get off his high horse and give us some convenient syntax. In the meantime, I've come up with a shitty hack that is typical of what you see in Ruby. See my answer. – Throw Away Account May 13 '17 at 07:16
  • @ThrowawayAccount3Million: I don't follow what you mean by "get off this high horse". He wrote "I am not sure", how is that a "high horse"? The people in that discussion thread who argued for nested methods did not provide any convincing examples of where they actually would be useful, while several people in the discussion provided several examples of where the current behavior is useful. As a language designer, why would he backwards-incompatibly remove a feature that is demonstrably useful just to backwards-incompatibly add a feature for which no-one has demonstrated a use-case yet? – Jörg W Mittag May 13 '17 at 08:45
15

Actually it's possible. You can use procs/lambda for this.

def test(value)
  inner = ->() {
    value * value
  }
  inner.call()
end
SuperManEver
  • 2,263
  • 6
  • 28
  • 36
  • 2
    You're not wrong, but your answer is worded as a solution to achieve nested methods. When in reality you're just using procs which are not methods. It's a fine answer outside of claiming to solve "nested methods" – Brandon Buck May 01 '18 at 17:50
  • 2
    What would actual nested methods be able to do that this proc wouldn't, or would do poorly? – Felipe Müller Jun 15 '21 at 00:17
5

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"
iirekm
  • 8,890
  • 5
  • 36
  • 46
  • 10
    inner_method is not a method, it's a function/lambda/proc. There is no associated instance of any class so it is not a method. – Sami Samhuri May 25 '13 at 00:00
3

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.

Throw Away Account
  • 2,593
  • 18
  • 21
2

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.

mdmoskwa
  • 138
  • 5
0

There are no nested methods. All are instance methods are only defined as instance methods after running the method above them

irb(main):001:0> 
irb(main):002:1* class Test1
irb(main):003:2*   def meth1
irb(main):004:3*     def meth2
irb(main):005:3*       puts "Yay"
irb(main):006:2*     end
irb(main):007:3*     def meth3
irb(main):009:4*       def meth3_3
irb(main):010:4*         puts "Third level indented method"
irb(main):012:2*     end
irb(main):013:1*   end
irb(main):014:0> end
=> :meth1
irb(main):015:0> Test1.new.meth3_3
Traceback (most recent call last):
        4: from /home/khal/.rbenv/versions/2.7.1/bin/irb:23:in `<main>'
        3: from /home/khal/.rbenv/versions/2.7.1/bin/irb:23:in `load'
        2: from /home/khal/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/irb-1.2.3/exe/irb:11:in `<top (required)>'
        1: from (irb):15
NoMethodError (undefined method `meth3_3' for #<Test1:0x0000562ae163ce48>)
Did you mean?  meth1
irb(main):016:0> Test1.new.meth3
Traceback (most recent call last):
        5: from /home/khal/.rbenv/versions/2.7.1/bin/irb:23:in `<main>'
        4: from /home/khal/.rbenv/versions/2.7.1/bin/irb:23:in `load'
        3: from /home/khal/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/irb-1.2.3/exe/irb:11:in `<top (required)>'
        2: from (irb):15
        1: from (irb):16:in `rescue in irb_binding'
NoMethodError (undefined method `meth3' for #<Test1:0x0000562ae1328658>)
Did you mean?  meth1
               method
irb(main):017:0> Test1.new.meth2
Traceback (most recent call last):
        5: from /home/khal/.rbenv/versions/2.7.1/bin/irb:23:in `<main>'
        4: from /home/khal/.rbenv/versions/2.7.1/bin/irb:23:in `load'
        3: from /home/khal/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/irb-1.2.3/exe/irb:11:in `<top (required)>'
        2: from (irb):16
        1: from (irb):17:in `rescue in irb_binding'
NoMethodError (undefined method `meth2' for #<Test1:0x0000562ae163df78>)
Did you mean?  meth1
               method
irb(main):018:0> Test1.new.meth1
=> :meth3
irb(main):019:0> Test1.new.meth3_3
Traceback (most recent call last):
        4: from /home/khal/.rbenv/versions/2.7.1/bin/irb:23:in `<main>'
        3: from /home/khal/.rbenv/versions/2.7.1/bin/irb:23:in `load'
        2: from /home/khal/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/irb-1.2.3/exe/irb:11:in `<top (required)>'
        1: from (irb):19
NoMethodError (undefined method `meth3_3' for #<Test1:0x0000562ae2568688>)
Did you mean?  meth3
irb(main):020:0> Test1.new.meth3
Method Drei
=> :meth3_3
irb(main):021:0> Test1.new.meth3_3
Third level indented method
=> nil
irb(main):022:0> 

initialy if your check the instance methods, you get:

irb(main):019:0> Test1.instance_methods
=> [:meth1, :dup, ...]

After running them in steps:

> Test1.instance_methods
=> [:meth3_3, :meth3, :meth1, :meth2,...]
Calvin Odira
  • 61
  • 1
  • 3
-3

:-D

Ruby has nested methods, only they don't do what you'd expect them to

1.9.3p484 :001 > def kme; 'kme'; def foo; 'foo'; end; end              
 => nil 
1.9.3p484 :003 >   self.methods.include? :kme
 => true 
1.9.3p484 :004 > self.methods.include? :foo
 => false 
1.9.3p484 :005 > kme
 => nil 
1.9.3p484 :006 > self.methods.include? :foo
 => true 
1.9.3p484 :007 > foo
 => "foo" 
bbozo
  • 7,075
  • 3
  • 30
  • 56