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

89

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

Поездка
источник
2
Можете ли вы поделиться образцом кода или хотя бы логическим эквивалентом того, что вы пытаетесь сделать.
Аарон Скраггс,

Ответы:

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 или запустить рекурсию.
Phrogz
119
У меня такое чувство, что у Ruby может не быть вложенных методов.
Марк Томас
16
@Mark Thomas: Я забыл упомянуть, что Ruby не имеет вложенных методов? :-) Серьезно: на тот момент, когда я написал этот ответ, было уже три ответа, каждый из которых утверждал, что Ruby действительно имеет вложенные методы. Некоторые из этих ответов даже получили положительные отзывы, несмотря на то, что были явно ошибочными. Один даже был принят OP, опять же, несмотря на то, что он ошибался. Фрагмент кода, который используется в ответе, чтобы доказать, что Ruby поддерживает вложенные методы, на самом деле доказывает обратное, но, очевидно, ни сторонники, ни OP не потрудились проверить. Итак, я дал один правильный ответ на каждый неправильный. :-)
Jörg W Mittag
10
Когда вы понимаете, что это всего лишь инструкции для ядра, которые изменяют таблицы, и что методы, классы и модули - это просто записи в таблицах, а не на самом деле, вы становитесь похожими на Нео, когда он видит, как выглядит Матрица. Тогда вы действительно могли бы стать философом и сказать, что кроме вложенных методов нет даже методов. Нет даже агентов. Это программы в матрице. Даже тот сочный стейк, который вы едите, - это просто запись в таблице.
mydoghasworms 03
3
Нет никаких методов, ваш код - просто симуляция в Матрице
bbozo
13

На самом деле это возможно. Для этого вы можете использовать procs / lambda.

def test(value)
  inner = ->() {
    value * value
  }
  inner.call()
end
Супермен
источник
2
Вы не ошиблись, но ваш ответ сформулирован как решение для достижения вложенных методов. Когда на самом деле вы просто используете процедуры, которые не являются методами. Это прекрасный ответ, если не считать утверждения о «вложенных методах»,
Брэндон Бак,
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"
iirekm
источник
9
inner_method - это не метод, это функция / lambda / proc. Нет связанного экземпляра какого-либо класса, поэтому это не метод.
Сами Самхури
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 - подделать его с помощью запутанных хаков, которые заставят некоторых пользователей задуматься: «Какого черта это вообще работает?», В то время как менее любопытные просто запоминают синтаксис, необходимый для использования этой штуки. Если вы когда-либо использовали 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назван в честь формы Common Lisp flet, за исключением случаев, когда f«функция» mозначает «метод».

Вы используете это так:

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

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

Выбросить аккаунт
источник
-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" 
bbozo
источник
4
Это не вложенный метод ... см. Ответ Йорга Миттага для ясного понимания.
Hardik 05