У меня есть метод внутри метода. Внутренний метод зависит от выполняемого цикла переменных. Это плохая идея?
Ответы:
ОБНОВЛЕНИЕ: поскольку в последнее время этот ответ, похоже, вызвал некоторый интерес, я хотел указать, что ведется обсуждение трекера проблем 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 сильно против вложенных методов (но все же для удаления определений вложенных методов).
На самом деле это возможно. Для этого вы можете использовать procs / lambda.
def test(value)
inner = ->() {
value * value
}
inner.call()
end
Нет-нет, у 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"
Вы можете сделать что-то вроде этого
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
вызывается. Если вы хотите такого поведения, а иногда и хотите, это хороший способ добиться этого.
Способ 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, вы сможете далеко продвинуться к созданию такой ужасной мерзости.
:-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"