【问题标题】:Is it possible to have Methods inside Methods?方法中可以有方法吗?
【发布时间】:2011-06-19 08:23:02
【问题描述】:

我在方法中有一个方法。内部方法取决于正在运行的变量循环。这是个坏主意吗?

【问题讨论】:

  • 您能否分享代码示例或至少是您尝试做的逻辑等价物。

标签: ruby methods


【解决方案1】:

更新:由于这个答案最近似乎引起了一些兴趣,我想指出有discussion on the Ruby issue tracker to remove the feature discussed here, namely to forbid having method definitions inside a method body


不,Ruby 没有嵌套方法。

你可以这样做:

class Test1
  def meth1
    def meth2
      puts "Yay"
    end
    meth2
  end
end

Test1.new.meth1

但这不是嵌套方法。我再说一遍:Ruby 没有嵌套方法。

这是一个动态方法定义。当你运行meth1 时,meth1 的主体将被执行。正文恰好定义了一个名为meth2的方法,这就是为什么在运行一次meth1之后,你可以调用meth2

但是meth2 是在哪里定义的?好吧,很明显它没有定义为嵌套方法,因为在 Ruby 中没有嵌套方法。定义为Test1的实例方法:

Test1.new.meth2
# Yay

另外,显然每次运行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

简而言之:不,Ruby 支持嵌套方法。

还要注意,在 Ruby 中,方法体不能是闭包,只有块体可以。这几乎消除了嵌套方法的主要用例,因为即使 if Ruby 支持嵌套方法,您也不能在嵌套方法中使用外部方法的变量。


更新继续:然后,在 稍后 阶段,此语法可能会被重新用于向 Ruby 添加嵌套方法,其行为方式与我描述的一样:它们将被限定为它们的包含方法,即在其包含方法体之外不可见且不可访问。并且可能,他们可以访问其包含方法的词法范围。但是,如果您阅读我上面链接的讨论,您可以观察到 matz 严重反对嵌套方法(但仍用于删除嵌套方法定义)。

【讨论】:

  • 不过,您也可以展示如何在 DRYness 方法中创建闭包 lambda,或运行递归。
  • 我感觉 Ruby 可能没有嵌套方法。
  • @Mark Thomas:我忘了提到Ruby没有嵌套方法吗? :-) 说真的:在我写这个答案的时候,已经有三个答案,每一个都声称 Ruby 确实 有嵌套方法。尽管明显错误,其中一些答案甚至得到了支持。一个甚至被 OP 接受了,尽管是错误的。答案用来证明 Ruby 支持嵌套方法的代码 sn-p 实际上证明了相反的情况,但显然支持者和 OP 实际上都懒得检查。所以,我给每一个错误的答案一个正确的答案。 :-)
  • 当你意识到这些都只是给内核修改表的指令,而方法、类和模块都只是表中的条目而不是真正的实体时,当 Neo 看到矩阵看起来像。那你就真的可以哲学化了,说除了嵌套方法,连方法都没有。甚至没有代理。它们是矩阵中的程序。即使是你正在吃的那块多汁的牛排也只是餐桌上的一个条目。
  • 没有方法,你的代码只是矩阵中的模拟
【解决方案2】:

其实是可以的。您可以为此使用 procs/lambda。

def test(value)
  inner = ->() {
    value * value
  }
  inner.call()
end

【讨论】:

  • 你没有错,但你的答案被表述为实现嵌套方法的解决方案。实际上,您只是在使用不是方法的过程。除了声称解决“嵌套方法”之外,这是一个很好的答案
  • 实际的嵌套方法能做什么而这个过程不会,或者做得不好?
【解决方案3】:

不,不,Ruby 确实有嵌套方法。检查这个:

def outer_method(arg)
    outer_variable = "y"
    inner_method = lambda {
      puts arg
      puts outer_variable
    }
    inner_method[]
end

outer_method "x" # prints "x", "y"

【讨论】:

  • inner_method 不是一个方法,它是一个函数/lambda/proc。没有任何类的关联实例,因此它不是方法。
【解决方案4】:

你可以这样做

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

当您在编写 DSL 等需要在方法之间共享范围的事情时,这很有用。但除此之外,你最好做其他任何事情,因为正如其他答案所说,每当调用 outer 时,都会重新定义 inner 。如果您想要这种行为,有时您可能会这样做,这是获得它的好方法。

【讨论】:

    【解决方案5】:

    Ruby 的方法是用令人困惑的 hack 来伪造它,这会让一些用户想知道“这到底是怎么回事?”,而不太好奇的人会简单地记住使用该东西所需的语法。如果您曾经使用过 Rake 或 Rails,那么您一定见过这种东西。

    这是一个黑客:

    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
    

    它所做的是定义一个创建类并将其传递给块的顶级方法。该类使用method_missing 假装它具有您选择的名称的方法。它通过调用您必须提供的 lambda 来“实现”该方法。通过使用单字母名称命名对象,您可以最大限度地减少它需要的额外输入量(这与 Rails 在其schema.rb 中所做的事情相同)。 mlet 以 Common Lisp 形式 flet 命名,除了 f 代表“函数”,m 代表“方法”。

    你可以这样使用它:

    def outer
       mlet :inner, ->(x) { x*2 } do |c|
         c.inner 12
       end
    end
    

    可以制作一个类似的装置,允许在没有额外嵌套的情况下定义多个内部函数,但这需要你可能在 Rake 或 Rspec 的实现中找到的那种更丑陋的 hack。弄清楚 Rspec 的 let! 是如何工作的,会让你在创造如此可怕的可憎之物方面走得更远。

    【讨论】:

      【解决方案6】:

      没有嵌套方法。都是实例方法,运行上面的方法后才定义为实例方法

      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> 
      
      

      如果您最初检查实例方法,您会得到:

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

      分步运行后:

      > Test1.instance_methods
      => [:meth3_3, :meth3, :meth1, :meth2,...]
      

      【讨论】:

        【解决方案7】:

        :-D

        Ruby 有嵌套的方法,只是它们没有按照您的期望进行操作

        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" 
        

        【讨论】:

        • 这不是嵌套方法...请参阅 Jörg W Mittag 的答案以获得清晰的理解。
        猜你喜欢
        • 2020-02-06
        • 1970-01-01
        • 2012-08-25
        • 2010-10-28
        • 2015-07-21
        • 1970-01-01
        • 1970-01-01
        • 2013-05-20
        相关资源
        最近更新 更多