Возможно ли иметь методы внутри методов?

У меня есть метод внутри метода. Внутренний метод зависит от цикла переменной, который выполняется. Это плохая идея?

Ответ 1

ОБНОВЛЕНИЕ. Поскольку этот ответ, похоже, получил некоторый интерес в последнее время, я хотел бы указать, что существует обсуждение на трекер-проблеме с проблемой 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 сильно зависит от вложенных методов (но все же для удаления вложенных определений методов).

Ответ 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"

Ответ 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, которые требуют совместного использования области между методами. Но в остальном вам гораздо лучше делать что-либо еще, потому что, как говорят другие ответы, inner переопределяется всякий раз, когда вызывается outer. Если вы хотите этого поведения, и иногда можете, это хороший способ его получить.

Ответ 5

Путь Ruby заключается в том, чтобы подделать его с запутанными хаками, которые заставят некоторых пользователей задаться вопросом: "Как в fuck это даже работает?", в то время как менее любопытный просто запоминает синтаксис, необходимый для использования этой вещи. Если вы когда-либо использовали 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, чтобы притворяться, что у него есть метод с именем, которое вы выбрали. Он "реализует" метод, вызывая лямбда, которую вы должны предоставить. Назвав объект однобуквенным именем, вы можете свести к минимуму количество требуемого дополнительного ввода (что то же самое, что Rails делает в schema.rb). mlet назван в честь общей формы Lisp flet, за исключением тех случаев, когда f означает "функция", m означает "метод".

Вы используете его следующим образом:

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

Можно создать аналогичное приспособление, которое позволяет определять множество внутренних функций без дополнительного вложенности, но для этого требуется еще более уродливый взлом такого рода, который вы можете найти в реализации Rake или Rspec. Выяснение того, как работает Rspec let!, даст вам долгий путь к созданию такой ужасной мерзости.

Ответ 6

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