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


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?


74
2018-02-01 15:07


origen


Respuestas:


ACTUALIZACIÓN: dado que esta respuesta parece haber despertado interés últimamente, quería señalar que existe discusión sobre el rastreador de problemas de Ruby para retirar la característica discutida aquí, a saber, prohibir tener definiciones de métodos 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 eso es no un método anidado. Repito: Ruby no tiene métodos anidados

Lo que es esto, es una definición de método dinámico. Cuando corres meth1, el cuerpo de meth1 será ejecutado. El cuerpo simplemente define un método llamado meth2, que es por eso que después de correr meth1 una vez, puedes llamar meth2.

Pero donde esta meth2 definido? Bueno, obviamente es no definido como un método anidado, ya que no son métodos anidados en Ruby. Se define como un método de instancia de Test1:

Test1.new.meth2
# Yay

Además, obviamente será redefinido cada vez que corras 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 apoyar los 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 hacerlo. Esto prácticamente elimina el uso principal de los métodos anidados, ya que incluso Si Ruby admite métodos anidados, no puede usar las variables del método externo en el método anidado.


ACTUALIZACIÓN CONTINUACIÓN: en una luego etapa, entonces, esta sintaxis podría ser reutilizada para agregar métodos anidados a Ruby, que se comportaría de la manera que describí: se verían dentro del alcance de su método contenedor, es decir, invisible e inaccesible fuera de su cuerpo de método contenedor. Y posiblemente, tendrían acceso al alcance léxico de su método contenedor. Sin embargo, si lee la discusión que he vinculado 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).


140
2018-02-01 16:27



En realidad es posible. Puede usar procs / lambda para esto.

def test(value)
  inner = ->() {
    value * value
  }
  inner.call()
end

9
2018-03-02 20:41



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"

4
2018-01-05 19:55



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 haces cosas como escribir DSL que requieren compartir el alcance entre los métodos. Pero de lo contrario, es mucho mejor que hagas otra cosa, porque como dijeron las otras respuestas, inner se redefine cada vez outer es invocado Si quieres este comportamiento, y a veces puedes, esta es una buena forma de obtenerlo.


1
2018-06-15 21:13



La manera de Ruby es falsificarla con hacks 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 usarla. Si alguna vez has usado Rake o Rails, has visto este tipo de cosas.

Aquí hay un truco como este:

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_missingpretender 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 tipeo adicional que requiere (que es lo mismo que 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 crear un artilugio similar que permita definir múltiples funciones internas sin anidamiento adicional, pero eso requiere un corte aún más feo del que podría encontrar en la implementación de Rake o Rspec. Averiguar cómo Rspec let! las obras te ayudarán a crear una abominación tan horrible.


0
2018-05-13 07:21



:-RE

Ruby tiene métodos anidados, solo que no hacen lo que esperas que hagan

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" 

-2
2018-01-31 14:29