web-dev-qa-db-fra.com

Est-il possible d'avoir des méthodes à l'intérieur des méthodes?

J'ai une méthode à l'intérieur d'une méthode. La méthode intérieure dépend d'une boucle variable en cours d'exécution. C'est une mauvaise idée?

83
Trip

MISE À JOUR: Comme cette réponse semble avoir suscité un certain intérêt ces derniers temps, je voulais souligner qu'il y a discussion sur le Ruby tracker problème à supprimer la fonctionnalité discutée ici, à savoir interdire d'avoir des définitions de méthode dans un corps de méthode .


Non, Ruby n'a pas de méthodes imbriquées.

Vous pouvez faire quelque chose comme ça:

class Test1
  def meth1
    def meth2
      puts "Yay"
    end
    meth2
  end
end

Test1.new.meth1

Mais ce n'est pas une méthode imbriquée. Je répète: Ruby n'a pas de méthodes imbriquées.

Il s'agit d'une définition de méthode dynamique. Lorsque vous exécutez meth1, le corps de meth1 sera exécuté. Le corps arrive juste à définir une méthode nommée meth2, c'est pourquoi après avoir exécuté meth1 une fois, vous pouvez appeler meth2.

Mais où est meth2 défini? Eh bien, ce n'est évidemment pas défini comme une méthode imbriquée, car il n'y a pas des méthodes imbriquées dans Ruby. Il est défini comme une méthode d'instance de Test1:

Test1.new.meth2
# Yay

De plus, il sera évidemment redéfini à chaque fois que vous exécutez 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 bref: non, Ruby ne prend pas en charge les méthodes imbriquées.

Notez également que dans Ruby, les corps de méthode ne peuvent pas être des fermetures, seuls les corps de bloc peuvent l'être. Cela élimine à peu près le cas d'utilisation majeur pour les méthodes imbriquées, car même si Ruby les méthodes imbriquées prises en charge, vous ne pourriez pas ' t utilisez les variables de la méthode externe dans la méthode imbriquée.


MISE À JOUR SUITE: à une étape plus tard , alors, cette syntaxe pourrait être réutilisée pour ajouter des méthodes imbriquées à Ruby, qui se comporterait comme je l'ai décrit: ils seraient limités à leur méthode de confinement, c'est-à-dire invisibles et inaccessibles en dehors de leur corps de méthode de confinement. Et éventuellement, ils auraient accès à la portée lexicale de leur méthode conteneur. Cependant, si vous lisez la discussion que j'ai liée ci-dessus, vous pouvez observer que matz est fortement contre les méthodes imbriquées (mais toujours pour supprimer les définitions de méthodes imbriquées).

157
Jörg W Mittag

En fait, c'est possible. Vous pouvez utiliser procs/lambda pour cela.

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

Non, non, Ruby a des méthodes imbriquées. Vérifiez ceci:

def outer_method(arg)
    outer_variable = "y"
    inner_method = lambda {
      puts arg
      puts outer_variable
    }
    inner_method[]
end

outer_method "x" # prints "x", "y"
5
iirekm

Vous pouvez faire quelque chose comme ça

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

Ceci est utile lorsque vous faites des choses comme l'écriture de DSL qui nécessitent le partage de la portée entre les méthodes. Mais sinon, vous feriez bien mieux de faire autre chose, car comme les autres réponses l'ont dit, inner est redéfini chaque fois que outer est invoqué. Si vous voulez ce comportement, et vous le pouvez parfois, c'est un bon moyen de l'obtenir.

1
mdmoskwa

La manière Ruby consiste à truquer avec des hacks déroutants qui amèneront certains utilisateurs à se demander "Comment ça marche même?", Tandis que les moins curieux mémoriseront simplement la syntaxe nécessaire pour utiliser le Si vous avez déjà utilisé Rake ou Rails, vous avez vu ce genre de chose.

Voici un tel 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

Cela permet de définir une méthode de niveau supérieur qui crée une classe et la transmet à un bloc. La classe utilise method_missing Pour prétendre qu'elle a une méthode avec le nom que vous avez choisi. Il "implémente" la méthode en appelant le lambda que vous devez fournir. En nommant l'objet avec un nom à une lettre, vous pouvez minimiser la quantité de frappe supplémentaire qu'il nécessite (ce qui est la même chose que Rails fait dans son schema.rb)). mlet est nommé d'après la forme LISP commune flet, sauf lorsque f signifie "fonction", m signifie "méthode".

Vous l'utilisez comme ceci:

def outer
   mlet :inner, ->(x) { x*2 } do |c|
     c.inner 12
   end
end

Il est possible de créer un engin similaire qui permet de définir plusieurs fonctions internes sans imbrication supplémentaire, mais qui nécessite un hack encore plus laid du type que vous pourriez trouver dans l'implémentation de Rake ou Rspec. Comprendre le fonctionnement de let! De Rspec vous permettrait de créer une horrible abomination.

0