Warum unterstützt Ruby nicht das Überladen von Methoden?

Anstatt das Überladen von Methoden zu unterstützen, überschreibt Ruby vorhandene Methoden. Kann jemand erklären, warum die Sprache so entworfen wurde?

Das Überladen von Methoden kann erreicht werden, indem zwei Methoden mit demselben Namen und unterschiedlichen Signaturen deklariert werden. Diese verschiedenen Signaturen können entweder

  1. Argumente mit unterschiedlichen Datentypen, zB: method(int a, int b) vs method(String a, String b)
  2. Variable Anzahl von Argumenten, zB: method(a) vs method(a, b)

Wir können das Überladen von Methoden nicht auf dem ersten Weg erreichen, da es keine Datentypen-Deklaration in Ruby ( dynamisch typisierte Sprache ) gibt. Die einzige Möglichkeit, die obige Methode zu definieren, ist def(a,b)

Mit der zweiten Option könnte es so aussehen, als könnten wir eine Überladung der Methode erreichen, aber das können wir nicht. Sagen wir, ich habe zwei Methoden mit unterschiedlicher Anzahl von Argumenten,

 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. 

Daher muss Ruby eine Methode in der Methoden-Suchkette mit einem eindeutigen Namen beibehalten.

“Overloading” ist ein Begriff, der in Ruby einfach keinen Sinn ergibt. Es ist im Grunde ein Synonym für “static argument-based dispatch”, aber Ruby hat überhaupt keinen statischen Versand. Der Grund, warum Ruby den statischen Versand basierend auf den Argumenten nicht unterstützt, liegt darin, dass der statische Versand nicht unterstützt wird. Es unterstützt keine statische Verteilung jeglicher Art , ob auf Argumente oder auf andere Weise.

Nun, wenn Sie nicht wirklich speziell nach Überladung fragen, sondern vielleicht nach dynamischer argumentbasierter Verteilung, dann lautet die Antwort: weil Matz es nicht implementiert hat. Weil niemand sich darum bemüht hat, es vorzuschlagen. Weil sich niemand sonst darum gekümmert hat.

Im Allgemeinen ist es sehr schwierig, eine dynamische argumentbasierte Verteilung in einer Sprache mit optionalen Argumenten und Variablenlängen-Argumentlisten zu bekommen, und es ist noch schwieriger , sie verständlich zu halten. Selbst in Sprachen mit statischer argument-basierter Versendung und ohne optionale Argumente (wie zum Beispiel Java) ist es manchmal fast unmöglich, für einen bloßen Sterblichen zu sagen, welche Überladung ausgewählt wird.

In C # können Sie tatsächlich jedes 3-SAT-Problem in eine Überladungsauflösung codieren, was bedeutet, dass die Überladungsauflösung in C # NP-schwer ist.

Versuchen Sie das jetzt mit dynamischem Versand, wo Sie die zusätzliche Zeitdimension im Kopf behalten.

Es gibt Sprachen, die dynamisch basierend auf allen Argumenten einer Prozedur versenden, im Gegensatz zu objektorientierten Sprachen, die nur auf dem “versteckten” nullten Selbstargument versenden. Common Lisp beispielsweise triggers die dynamischen Typen und sogar die dynamischen Werte aller Argumente aus. Clojure versendet eine beliebige function aller Argumente (BTW ist extrem cool und extrem mächtig).

Aber ich kenne keine OO-Sprache mit dynamischem argumentbasiertem Versand. Martin Odersky sagte, dass er in Erwägung ziehen könnte , argumentbasiertes Dispatch zu Scala hinzuzufügen, aber nur wenn er gleichzeitig Überlastung entfernen und sowohl mit existierendem Scala-Code, der Überladung verwendet und mit Java kompatibel ist, rückwärtskompatibel sein kann (er erwähnte insbesondere Swing und AWT) die extrem komplizierte Tricks spielen und so ziemlich jeden fiesen dunklen Fall von Javas ziemlich komplexen Überladungsregeln trainieren. Ich hatte selbst ein paar Ideen, wie man argumentbasiertes Dispatch zu Ruby hinzufügen kann, aber ich konnte nie herausfinden, wie man es rückwärtskompatibel macht.

Ich nehme an, Sie suchen nach der Fähigkeit, dies zu tun:

 def my_method(arg1) .. end def my_method(arg1, arg2) .. end 

Ruby unterstützt das auf andere Weise:

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

Ein gängiges Muster ist auch, Optionen als Hash zu übergeben:

 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' 

Ich hoffe, das hilft

Das Überladen von Methoden ist in einer Sprache mit statischer Typisierung sinnvoll, bei der Sie verschiedene Arten von Argumenten unterscheiden können

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

sowie zwischen verschiedenen Anzahl von Argumenten

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

Die erste Unterscheidung existiert nicht in Ruby. Ruby verwendet dynamische Typisierung oder “Duck Typing”. Die zweite Unterscheidung kann durch Standardargumente oder durch Arbeiten mit Argumenten gehandhabt werden:

 def f(n, s = 'foo', flux_compensator = true) ... end def f(*args) case args.size when ... when 2 ... when 3 ... end end 

Dies beantwortet nicht die Frage, warum Ruby keine Methodenüberladung hat, aber Bibliotheken von Drittanbietern können es bereitstellen.

Die contracts.ruby- Bibliothek ermöglicht das Überladen. Beispiel aus dem Tutorial:

 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 

Beachten Sie, dass dies tatsächlich mächtiger ist als das Überladen von Java, da Sie Werte angeben können, die übereinstimmen (z. B. 1 ), nicht nur Typen.

Sie werden jedoch eine verminderte performance feststellen. Sie müssen Benchmarks ausführen, um zu entscheiden, wie viel Sie tolerieren können.

Ich mache oft die folgende Struktur:

 def method(param) case param when String method_for_String(param) when Type1 method_for_Type1(param) ... else #default implementation end end 

Dadurch kann der Benutzer des Objekts die Methode method_name: clean und clear verwenden. Wenn er jedoch die Ausführung optimieren möchte, kann er direkt die richtige Methode aufrufen.

Außerdem macht es deinen Test cleaner und besser.