Eu tenho um método dentro de um método. O método interior depende de um loop variável que está sendo executado. Isso é uma má ideia?
Respostas:
ATUALIZAÇÃO: uma vez que esta resposta parece ter despertado algum interesse ultimamente, eu gostaria de apontar que há uma discussão sobre o rastreador de problemas Ruby para remover o recurso discutido aqui, ou seja, proibir ter definições de método dentro de um corpo de método .
Não, Ruby não tem métodos aninhados.
Você pode fazer algo assim:
class Test1
def meth1
def meth2
puts "Yay"
end
meth2
end
end
Test1.new.meth1
Mas esse não é um método aninhado. Repito: Ruby não tem métodos aninhados.
O que isto é, é uma definição de método dinâmico. Quando você executa meth1
, o corpo de meth1
será executado. O corpo apenas define um método chamado meth2
, e é por isso que, depois de executar meth1
uma vez, você pode chamar meth2
.
Mas onde está meth2
definido? Bem, obviamente não é definido como um método aninhado, uma vez que não existem métodos aninhados em Ruby. É definido como um método de instância de Test1
:
Test1.new.meth2
# Yay
Além disso, obviamente será redefinido sempre que você executar 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
Resumindo: não, Ruby não suporta métodos aninhados.
Observe também que em Ruby, corpos de método não podem ser fechamentos, apenas corpos de bloco podem. Isso praticamente elimina o principal caso de uso para métodos aninhados, já que mesmo se Ruby suportasse métodos aninhados, você não poderia usar as variáveis do método externo no método aninhado.
ATUALIZAÇÃO CONTINUADA: em um estágio posterior , então, esta sintaxe pode ser reutilizada para adicionar métodos aninhados ao Ruby, que se comportariam da maneira que descrevi: eles teriam como escopo seu método de contenção, ou seja, invisível e inacessível fora de seu método de contenção corpo. E, possivelmente, eles teriam acesso ao escopo léxico do método de contenção. No entanto, se você ler a discussão que vinculei acima, poderá observar que matz é fortemente contra métodos aninhados (mas ainda para remover definições de métodos aninhados).
Na verdade, é possível. Você pode usar procs / lambda para isso.
def test(value)
inner = ->() {
value * value
}
inner.call()
end
Não, não, Ruby tem métodos aninhados. Verifique isto:
def outer_method(arg)
outer_variable = "y"
inner_method = lambda {
puts arg
puts outer_variable
}
inner_method[]
end
outer_method "x" # prints "x", "y"
Você pode fazer algo assim
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
Isso é útil quando você está fazendo coisas como escrever DSLs que requerem compartilhamento de escopo entre métodos. Mas, do contrário, é muito melhor você fazer qualquer outra coisa, porque, como as outras respostas disseram, inner
é redefinido sempre que outer
for invocado. Se você deseja esse comportamento, e às vezes deseja, esta é uma boa maneira de obtê-lo.
O jeito do Ruby é fingir com hacks confusos que farão alguns usuários se perguntarem "Como diabos isso funciona?", Enquanto os menos curiosos simplesmente memorizarão a sintaxe necessária para usar a coisa. Se você já usou Rake ou Rails, já viu esse tipo de coisa.
Aqui está um tal 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
O que isso faz é definir um método de nível superior que cria uma classe e a passa para um bloco. A classe costuma method_missing
fingir que possui um método com o nome que você escolheu. Ele "implementa" o método chamando o lambda que você deve fornecer. Ao nomear o objeto com um nome de uma letra, você pode minimizar a quantidade de digitação extra que ele requer (que é a mesma coisa que o Rails faz nele schema.rb
). mlet
é nomeado após a forma Common Lisp flet
, exceto onde f
significa "função", m
significa "método".
Você o usa assim:
def outer
mlet :inner, ->(x) { x*2 } do |c|
c.inner 12
end
end
É possível fazer uma engenhoca semelhante que permite que várias funções internas sejam definidas sem aninhamento adicional, mas que requer um hack ainda mais feio do tipo que você pode encontrar na implementação de Rake ou Rspec. Descobrir como o Rspec let!
funciona levaria você a um longo caminho para ser capaz de criar uma abominação tão horrível.
:-D
Ruby tem métodos aninhados, só que eles não fazem o que você espera que façam
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"