Est-il possible d'avoir des méthodes dans des méthodes?

89

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. Est-ce une mauvaise idée?

Voyage
la source
2
Pouvez-vous partager l'exemple de code ou au moins un équivalent logique de ce que vous essayez de faire.
Aaron Scruggs

Réponses:

165

MISE À JOUR: Puisque cette réponse semble avoir suscité un certain intérêt ces derniers temps, je voulais souligner qu'il y a une discussion sur le suivi des problèmes Ruby pour 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 ceci:

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 le répète: Ruby n'a pas de méthodes imbriquées.

Ce que c'est, est une définition de méthode dynamique. Lorsque vous exécutez meth1, le corps de meth1sera exécuté. Le corps définit simplement une méthode nommée meth2, c'est pourquoi après avoir exécuté meth1une fois, vous pouvez appeler meth2.

Mais où est meth2défini? Eh bien, ce n'est évidemment pas défini comme une méthode imbriquée, car il n'y a pas de 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écuterez 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 le peuvent. Cela élimine à peu près le cas d'utilisation majeur des méthodes imbriquées, car même si Ruby supportait les méthodes imbriquées, vous ne pouviez pas utiliser les variables de la méthode externe dans la méthode imbriquée.


MISE À JOUR SUITE: à un stade ultérieur , cette syntaxe pourrait être réutilisée pour ajouter des méthodes imbriquées à Ruby, qui se comporteraient comme je l'ai décrit: elles seraient limitées à leur méthode contenant, c'est-à-dire invisibles et inaccessibles en dehors de leur méthode contenant corps. Et éventuellement, ils auraient accès à la portée lexicale de leur méthode contenante. 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).

Jörg W Mittag
la source
6
Vous pouvez également montrer, cependant, comment créer un lambda de fermeture dans une méthode pour DRYness, ou pour exécuter la récursivité.
Phrogz
119
J'ai l'impression que Ruby n'a peut-être pas de méthodes imbriquées.
Mark Thomas
16
@Mark Thomas: Ai-je oublié de mentionner que Ruby n'a pas de méthodes imbriquées? :-) Sérieusement: au moment où je l' ai écrit cette réponse, il y avait déjà trois réponses, chacun de qui prétendait que Ruby n'ont des méthodes imbriquées. Certaines de ces réponses ont même eu des votes positifs, bien qu'elles soient manifestement fausses. L'une a même été acceptée par l'OP, encore une fois, malgré son erreur. L'extrait de code utilisé par cette réponse pour prouver que Ruby prend en charge les méthodes imbriquées prouve en fait le contraire, mais apparemment ni les votants positifs ni l'OP n'ont pris la peine de vérifier. Donc, j'ai donné une bonne réponse pour chaque mauvaise. :-)
Jörg W Mittag
10
Lorsque vous réalisez que ce ne sont que des instructions au noyau qui modifient les tables et que les méthodes, les classes et les modules ne sont que des entrées dans des tables et pas vraiment réelles, vous devenez comme Neo quand il voit à quoi ressemble la matrice. Alors vous pourriez vraiment devenir philosophique et dire qu'à part les méthodes imbriquées, il n'y a même pas de méthodes. Il n'y a même pas d'agents. Ce sont des programmes dans la matrice. Même ce steak juteux que vous mangez n'est qu'une entrée dans une table.
mydoghasworms
3
Il n'y a pas de méthodes, votre code n'est qu'une simulation dans The Matrix
bbozo
13

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

def test(value)
  inner = ->() {
    value * value
  }
  inner.call()
end
SuperManEver
la source
2
Vous n'avez pas tort, mais votre réponse est formulée comme une solution pour obtenir des méthodes imbriquées. Quand en réalité vous n'utilisez que des procs qui ne sont pas des méthodes. C'est une bonne réponse en dehors de prétendre résoudre des "méthodes imbriquées"
Brandon Buck
5

Non, non, Ruby a des méthodes imbriquées. Vérifie ça:

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
la source
9
inner_method n'est pas une méthode, c'est une fonction / lambda / proc. Il n'y a aucune instance associée à aucune classe, donc ce n'est pas une méthode.
Sami Samhuri
2

Tu peux 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 portée entre les méthodes. Mais sinon, vous feriez bien mieux de faire autre chose, car comme le disent les autres réponses, il innerest redéfini chaque fois qu'il outerest invoqué. Si vous voulez ce comportement, et vous pourriez parfois le faire, c'est un bon moyen de l'obtenir.

mdmoskwa
la source
2

La méthode Ruby est de le simuler avec des hacks déroutants qui amèneront certains utilisateurs à se demander «Comment ça marche, putain?», Tandis que les moins curieux mémoriseront simplement la syntaxe nécessaire pour utiliser la chose. 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 définit une méthode de niveau supérieur qui crée une classe et la transmet à un bloc. La classe utilise method_missingpour 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 d'une lettre, vous pouvez minimiser la quantité de saisie supplémentaire qu'il nécessite (ce qui est la même chose que Rails fait dans son schema.rb). mletest nommé d'après la forme Common Lisp flet, sauf où fsignifie «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 cela nécessite un hack encore plus laid du type que vous pourriez trouver dans l'implémentation de Rake ou de Rspec. Comprendre comment les let!travaux de Rspec vous aideraient à créer une abomination aussi horrible.

Compte à jeter
la source
-3

:-RÉ

Ruby a des méthodes imbriquées, mais elles ne font pas ce que vous attendez d'elles

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
la source
4
Ce n'est pas une méthode imbriquée ... voir la réponse de Jörg W Mittag pour une compréhension claire.
Hardik