Tengo un método dentro de un método. El método interior depende de un bucle variable que se esté ejecutando. ¿Es una mala idea?
Respuestas:
ACTUALIZACIÓN: dado que esta respuesta parece haber despertado cierto interés últimamente, quería señalar que existe una discusión sobre el rastreador de problemas de Ruby para eliminar la característica discutida aquí, es decir, prohibir tener definiciones de método dentro de un cuerpo de método .
No, Ruby no tiene métodos anidados.
Puedes hacer algo como esto:
class Test1
def meth1
def meth2
puts "Yay"
end
meth2
end
end
Test1.new.meth1
Pero ese no es un método anidado. Repito: Ruby no tiene métodos anidados.
Esto es una definición de método dinámico. Cuando corras meth1
, se ejecutará el cuerpo de meth1
. El cuerpo simplemente define un método llamado meth2
, por lo que después de ejecutarlo meth1
una vez, puede llamar meth2
.
¿Pero dónde se meth2
define? Bueno, obviamente no está definido como un método anidado, ya que no hay métodos anidados en Ruby. Se define como un método de instancia de Test1
:
Test1.new.meth2
# Yay
Además, obviamente se redefinirá cada vez que ejecute 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
En resumen: no, Ruby no admite métodos anidados.
Tenga en cuenta también que en Ruby, los cuerpos de método no pueden ser cierres, solo los cuerpos de bloque pueden. Esto prácticamente elimina el caso de uso principal de los métodos anidados, ya que incluso si Ruby admitiera métodos anidados, no podría usar las variables del método externo en el método anidado.
ACTUALIZACIÓN CONTINUADA: en una etapa posterior , entonces, esta sintaxis podría reutilizarse para agregar métodos anidados a Ruby, que se comportarían de la manera que describí: estarían sujetos a su método contenedor, es decir, invisibles e inaccesibles fuera de su método contenedor cuerpo. Y posiblemente, tendrían acceso al alcance léxico de su método contenedor. Sin embargo, si lee la discusión que vinculé anteriormente, puede observar que matz está muy en contra de los métodos anidados (pero aún así para eliminar las definiciones de métodos anidados).
De hecho, es posible. Puede usar procs / lambda para esto.
def test(value)
inner = ->() {
value * value
}
inner.call()
end
No, no, Ruby tiene métodos anidados. Mira esto:
def outer_method(arg)
outer_variable = "y"
inner_method = lambda {
puts arg
puts outer_variable
}
inner_method[]
end
outer_method "x" # prints "x", "y"
Puedes hacer algo como esto
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
Esto es útil cuando está haciendo cosas como escribir DSL que requieren compartir el alcance entre métodos. Pero de lo contrario, es mucho mejor hacer cualquier otra cosa, porque, como dicen las otras respuestas, inner
se redefine cada vez que outer
se invoca. Si desea este comportamiento, y a veces puede hacerlo, esta es una buena manera de obtenerlo.
La forma de Ruby es falsificarlo con trucos confusos que harán que algunos usuarios se pregunten "¿Cómo diablos funciona esto?", Mientras que los menos curiosos simplemente memorizarán la sintaxis necesaria para usar la cosa. Si alguna vez ha usado Rake o Rails, ha visto este tipo de cosas.
Aquí hay un truco de este tipo:
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
Lo que hace es definir un método de nivel superior que crea una clase y la pasa a un bloque. La clase usa method_missing
para fingir que tiene un método con el nombre que eligió. "Implementa" el método llamando a la lambda que debe proporcionar. Al nombrar el objeto con un nombre de una letra, puede minimizar la cantidad de escritura adicional que requiere (que es lo mismo que hace Rails en su schema.rb
). mlet
lleva el nombre de la forma Common Lisp flet
, excepto donde f
significa "función", m
significa "método".
Lo usas así:
def outer
mlet :inner, ->(x) { x*2 } do |c|
c.inner 12
end
end
Es posible hacer un artilugio similar que permita definir múltiples funciones internas sin anidamiento adicional, pero eso requiere un truco aún más feo del tipo que puede encontrar en la implementación de Rake o Rspec. Averiguar cómo funciona Rspec te let!
ayudaría mucho a crear una abominación tan horrible.
:-RE
Ruby tiene métodos anidados, solo que no hacen lo que esperarías que hicieran
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"