Pourquoi ruby ​​ne prend-il pas en charge la surcharge des méthodes?


146

Au lieu de prendre en charge la surcharge de méthode, Ruby écrase les méthodes existantes. Quelqu'un peut-il expliquer pourquoi le langage a été conçu de cette façon?

Réponses:


166

La surcharge de méthode peut être obtenue en déclarant deux méthodes avec le même nom et des signatures différentes. Ces différentes signatures peuvent être soit,

  1. Arguments avec différents types de données, par exemple: method(int a, int b) vs method(String a, String b)
  2. Nombre variable d'arguments, par exemple: method(a) vs method(a, b)

Nous ne pouvons pas réaliser de surcharge de méthode en utilisant la première méthode car il n'y a pas de déclaration de type de données dans ruby ​​( langage typé dynamique ). Donc, la seule façon de définir la méthode ci-dessus estdef(a,b)

Avec la deuxième option, il peut sembler que nous pouvons réaliser une surcharge de méthode, mais nous ne pouvons pas. Disons que j'ai deux méthodes avec un nombre d'arguments différent,

def method(a); end;
def method(a, b = true); end; # second argument has a default value

method(10)
# Now the method call can match the first one as well as the second one, 
# so here is the problem.

Ainsi, ruby ​​doit conserver une méthode dans la chaîne de recherche de méthode avec un nom unique.


22
La réponse de @ Jörg W Mittag, enterrée bien en dessous, vaut vraiment la peine d'être lue.
user2398029

1
Et la réponse de @Derek Ekins, enterrée encore plus loin, offre une alternative
Cyril Duchon-Doris

Note aux futurs rubisistes ... FWIW vous pouvez le faire avec les contrats gem egonschiele.github.io/contracts.ruby/#method-overloading
engineerDave

214

"Surcharge" est un terme qui n'a tout simplement pas de sens dans Ruby. Il est essentiellement synonyme de « expédition basé argument statique », mais Ruby n'a pas avoir l' envoi statique du tout . Donc, la raison pour laquelle Ruby ne prend pas en charge la distribution statique basée sur les arguments, c'est qu'il ne prend pas en charge la distribution statique, point final. Il ne prend en charge aucune distribution statique , qu'elle soit basée sur des arguments ou autre.

Maintenant, si vous ne parlez pas spécifiquement de la surcharge, mais peut-être de la répartition dynamique basée sur des arguments, alors la réponse est: parce que Matz ne l'a pas implémentée. Parce que personne d'autre n'a pris la peine de le proposer. Parce que personne d'autre ne s'est donné la peine de le mettre en œuvre.

En général, une répartition dynamique basée sur des arguments dans un langage avec des arguments optionnels et des listes d'arguments de longueur variable est très difficile à obtenir correctement, et encore plus difficile à garder compréhensible. Même dans les langages avec une répartition statique basée sur des arguments et sans arguments optionnels (comme Java, par exemple), il est parfois presque impossible de dire pour un simple mortel, quelle surcharge va être choisie.

En C #, vous pouvez en fait encoder n'importe quel problème 3-SAT en résolution de surcharge, ce qui signifie que la résolution de surcharge en C # est NP-hard.

Maintenant, essayez cela avec une répartition dynamique , où vous avez la dimension de temps supplémentaire à garder dans votre tête.

Il existe des langages qui distribuent dynamiquement en fonction de tous les arguments d'une procédure, par opposition aux langages orientés objet, qui ne diffusent que sur l' selfargument zéro "caché" . Common Lisp, par exemple, distribue les types dynamiques et même les valeurs dynamiques de tous les arguments. Clojure distribue sur une fonction arbitraire de tous les arguments (ce qui BTW est extrêmement cool et extrêmement puissant).

Mais je ne connais aucun langage OO avec une répartition dynamique basée sur des arguments. Martin Odersky a déclaré qu'il pourrait envisager d'ajouter une répartition basée sur les arguments à Scala, mais seulement s'il peut supprimer la surcharge en même temps et être rétrocompatible avec le code Scala existant qui utilise la surcharge et compatible avec Java (il a notamment mentionné Swing et AWT qui jouent des trucs extrêmement complexes exerçant à peu près tous les cas obscurs méchants des règles de surcharge assez complexes de Java). J'ai moi-même eu des idées sur l'ajout d'une répartition basée sur des arguments à Ruby, mais je n'ai jamais pu comprendre comment le faire d'une manière rétrocompatible.


5
C'est la bonne réponse. La réponse acceptée simplifie à l'extrême. C # a des paramètres nommés et des paramètres facultatifs et implémente toujours la surcharge, donc ce n'est pas aussi simple que " def method(a, b = true)ne fonctionnera pas, donc la surcharge de méthode est impossible." Ce n'est pas; c'est juste difficile. J'ai cependant trouvé CETTE réponse très informative.
tandrewnichols

1
@tandrewnichols: juste pour donner une idée à quel point la résolution de surcharge "difficile" est en C #… il est possible d'encoder n'importe quel problème 3-SAT comme résolution de surcharge en C # et de demander au compilateur de le résoudre au moment de la compilation, rendant ainsi la résolution de surcharge en C # NP -hard (3-SAT est connu pour être NP-complet). Imaginez maintenant devoir le faire non pas une fois par site d'appel au moment de la compilation, mais une fois par appel de méthode pour chaque appel de méthode au moment de l'exécution.
Jörg W Mittag

1
@ JörgWMittag Pourriez-vous inclure un lien montrant l'encodage d'un problème 3-SAT dans le mécanisme de résolution de surcharge?
Squidly


2
Il ne semble pas que «surcharge» soit synonyme de «répartition statique basée sur des arguments». La répartition statique basée sur des arguments est simplement l'implémentation la plus courante de la surcharge. La surcharge est un terme indépendant de l'implémentation signifiant «même nom de méthode mais différentes implémentations dans la même portée».
snovity

85

Je suppose que vous recherchez la capacité de le faire:

def my_method(arg1)
..
end

def my_method(arg1, arg2)
..
end

Ruby prend en charge cela d'une manière différente:

def my_method(*args)
  if args.length == 1
    #method 1
  else
    #method 2
  end
end

Un modèle courant consiste également à passer des options sous forme de hachage:

def my_method(options)
    if options[:arg1] and options[:arg2]
      #method 2
    elsif options[:arg1]
      #method 1
    end
end

my_method arg1: 'hello', arg2: 'world'

J'espère que cela pourra aider


15
+1 pour avoir fourni ce que beaucoup d'entre nous veulent juste savoir: comment travailler avec un nombre variable d'arguments dans les méthodes Ruby.
ashes999

3
Cette réponse peut bénéficier d'informations supplémentaires sur les arguments facultatifs. (Et peut-être aussi des arguments nommés, maintenant que ce sont une chose.)
Ajedi32

9

La surcharge de méthode a du sens dans un langage à typage statique, où vous pouvez distinguer différents types d'arguments

f(1)
f('foo')
f(true)

ainsi qu'entre différents nombres d'arguments

f(1)
f(1, 'foo')
f(1, 'foo', true)

La première distinction n'existe pas dans le rubis. Ruby utilise le typage dynamique ou "typage canard". La deuxième distinction peut être gérée par des arguments par défaut ou en travaillant avec des arguments:

def f(n, s = 'foo', flux_compensator = true)
   ...
end


def f(*args)
  case args.size
  when  
     ...
  when 2
    ...
  when 3
    ...
  end
end

Cela n'a rien à voir avec une frappe forte. Ruby est fortement typé, après tout.
Jörg W Mittag

8

Cela ne répond pas à la question de savoir pourquoi ruby ​​n'a pas de surcharge de méthode, mais des bibliothèques tierces peuvent la fournir.

La bibliothèque contracts.ruby permet la surcharge. Exemple adapté du tutoriel:

class Factorial
  include Contracts

  Contract 1 => 1
  def fact(x)
    x
  end

  Contract Num => Num
  def fact(x)
    x * fact(x - 1)
  end
end

# try it out
Factorial.new.fact(5)  # => 120

Notez que c'est en fait plus puissant que la surcharge de Java, car vous pouvez spécifier des valeurs à faire correspondre (par exemple 1), pas simplement des types.

Vous verrez cependant des performances réduites en utilisant ceci; vous devrez exécuter des benchmarks pour décider de ce que vous pouvez tolérer.


1
Dans les applications du monde réel avec tout type d'E / S, vous n'aurez qu'un ralentissement de 0,1 à 10% (selon le type d'E / S).
Waterlink

1

Je fais souvent la structure suivante:

def method(param)
    case param
    when String
         method_for_String(param)
    when Type1
         method_for_Type1(param)

    ...

    else
         #default implementation
    end
end

Cela permet à l'utilisateur de l'objet d'utiliser la méthode clean and clear nom_méthode: méthode Mais s'il souhaite optimiser l'exécution, il peut directement appeler la méthode correcte.

En outre, cela rend votre test plus clair et meilleur.


1

il y a déjà de bonnes réponses du côté pourquoi de la question. Cependant, si vous recherchez d'autres solutions, consultez la gemme rubis fonctionnelle qui est inspirée des fonctionnalités de correspondance de motifs Elixir .

 class Foo
   include Functional::PatternMatching

   ## Constructor Over loading
   defn(:initialize) { @name = 'baz' }
   defn(:initialize, _) {|name| @name = name.to_s }

   ## Method Overloading
   defn(:greet, :male) {
     puts "Hello, sir!"
   }

   defn(:greet, :female) {
     puts "Hello, ma'am!"
   }
 end

 foo = Foo.new or Foo.new('Bar')
 foo.greet(:male)   => "Hello, sir!"
 foo.greet(:female) => "Hello, ma'am!"   
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.