У меня есть метод внутри метода. Внутренний метод зависит от цикла переменной, который выполняется. Это плохая идея?
Возможно ли иметь методы внутри методов?
Ответ 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"