方法中是否可以包含方法?


89

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


2
您能否共享代码示例或至少与您尝试执行的逻辑等效?
亚伦·斯克鲁格斯

Answers:


165

更新:由于这个答案最近似乎引起了人们的兴趣,我想指出,关于Ruby问题跟踪器的讨论旨在删除此处讨论的功能,即,禁止在方法体内使用方法定义


不,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中,方法主体不能是闭包,只有块主体可以。这几乎消除了主要的使用案例嵌套方法,因为即使Ruby的支持嵌套的方法,你不能在嵌套方法使用外部方法的变量。


更新继续:然后,在稍后的阶段,可以将该语法重新用于向Ruby中添加嵌套方法,其行为方式与我描述的方式相同:它们的范围仅限于其包含方法,即在其包含方法之外不可见和不可访问身体。并且可能,他们将可以访问其包含方法的词法范围。但是,如果您阅读我上面链接的讨论,您会发现matz在很大程度上反对嵌套方法(但仍然适用于删除嵌套方法定义)。


6
但是,您可能还会显示如何在DRYness方法中创建闭包lambda或运行递归。
Phrogz

119
我感到Ruby可能没有嵌套的方法。
马克·托马斯

16
@Mark Thomas:我忘了提到Ruby没有嵌套方法吗?:-)说真的:在我编写此答案时,已经有三个答案,每个答案都声称Ruby确实具有嵌套方法。尽管公然错误,其中一些答案甚至遭到了批评。尽管错了,但其中一位甚至再次被OP接受。答案用来证明Ruby支持嵌套方法的代码片段实际上被证明是相反的,但是显然,支持者和OP都没有去检查。因此,对于每一个错误的答案,我都给出了正确的答案。:-)
约尔格W¯¯米塔格

10
当您意识到这些只是对内核的修改表的指令,而方法,类和模块都只是表中的条目而并非真实时,您会像Neo一样,当他看到Matrix的样子时。然后,您真的可以成为哲学家,并说除了嵌套方法之外,甚至没有方法。甚至没有代理商。它们是矩阵中的程序。即使您正在吃的多汁牛排也只是表格中的一项。
mydoghasworms

3
没有方法,您的代码只是Matrix中的模拟
bbozo 2014年

13

其实有可能。您可以为此使用procs / lambda。

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

2
您没有错,但是您的答案被表述为实现嵌套方法的解决方案。实际上,您只是在使用不是方法的proc。除了声称解决“嵌套方法”之外,这是一个很好的答案
布兰登·巴克

5

不,不,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"

9
inner_method不是方法,而是函数/ lambda / proc。没有任何类的关联实例,因此它不是方法。
Sami Samhuri 2013年

2

你可以做这样的事情

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之类的操作时,这很有用。但是否则,您最好不要做其他任何事情,因为正如其他答案所述,inner只要outer调用它就可以重新定义。如果您想要这种行为,有时可能会这样做,那么这是获得它的好方法。


2

Ruby的方法是使用令人迷惑的hack来伪造它,这会让一些用户怀疑“这到底是怎么工作的?”,而不太好奇的人只会记住使用该东西所需的语法。如果您曾经使用过Rake或Rails,那么您已经看到了这种事情。

这是一个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

这样做是定义一个顶级方法,该方法创建一个类并将其传递给块。该类用来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!工作原理将使您走出一条漫长的路要走,从而能够创造出如此可怕的可憎之处。


-3

:-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" 

4
这不是嵌套方法。请参阅JörgW Mittag的答案以清楚了解。
Hardik
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.