¿Es posible tener métodos dentro de métodos?

89

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?

Viaje
fuente
2
¿Puede compartir el ejemplo de código o al menos un equivalente lógico de lo que está intentando hacer?
Aaron Scruggs

Respuestas:

165

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 meth1una vez, puede llamar meth2.

¿Pero dónde se meth2define? 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).

Jörg W Mittag
fuente
6
Sin embargo, también puede mostrar cómo crear una lambda de cierre en un método para DRYness o ejecutar la recursividad.
Phrogz
119
Tengo la sensación de que Ruby podría no tener métodos anidados.
Mark Thomas
16
@Mark Thomas: ¿Olvidé mencionar que Ruby no tiene métodos anidados? :-) En serio: en el momento de escribir esta respuesta, ya había tres respuestas, cada uno de los cuales afirmaban que Ruby hace tener métodos anidados. Algunas de esas respuestas incluso obtuvieron votos positivos a pesar de estar descaradamente equivocadas. Uno incluso fue aceptado por el OP, nuevamente, a pesar de estar equivocado. El fragmento de código que usa la respuesta para demostrar que Ruby admite métodos anidados, en realidad demuestra lo contrario, pero aparentemente ni los votantes positivos ni el OP se molestaron en verificar. Entonces, di una respuesta correcta para cada respuesta incorrecta. :-)
Jörg W Mittag
10
Cuando se da cuenta de que todas estas son solo instrucciones para el Kernel que modifican tablas y que los métodos, clases y módulos son solo entradas en tablas y no son realmente reales, se vuelve como Neo cuando ve cómo se ve Matrix. Entonces podría volverse realmente filosófico y decir que además de los métodos anidados, ni siquiera hay métodos. Ni siquiera hay agentes. Son programas en la matriz. Incluso ese bistec jugoso que estás comiendo es solo una entrada en una mesa.
mydoghasworms
3
No hay métodos, tu código es solo una simulación en The Matrix
bbozo
13

De hecho, es posible. Puede usar procs / lambda para esto.

def test(value)
  inner = ->() {
    value * value
  }
  inner.call()
end
SuperManEver
fuente
2
No se equivoca, pero su respuesta está redactada como una solución para lograr métodos anidados. Cuando en realidad solo estás usando procs que no son métodos. Es una buena respuesta fuera de afirmar que resuelve "métodos anidados"
Brandon Buck
5

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"
iirekm
fuente
9
inner_method no es un método, es una función / lambda / proc. No hay una instancia asociada de ninguna clase, por lo que no es un método.
Sami Samhuri
2

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, innerse redefine cada vez que outerse invoca. Si desea este comportamiento, y a veces puede hacerlo, esta es una buena manera de obtenerlo.

mdmoskwa
fuente
2

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_missingpara 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). mletlleva el nombre de la forma Common Lisp flet, excepto donde fsignifica "función", msignifica "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.

Cuenta desechable
fuente
-3

:-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" 
bbozo
fuente
4
Este no es un método anidado ... consulte la respuesta de Jörg W Mittag para una comprensión clara.
Hardik