Warum wird die Ruby-Support-Methode nicht überladen?

146

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

Rambo
quelle

Antworten:

166

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

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

Wir können keine Methodenüberladung auf die erste Weise erreichen, da es in Ruby ( dynamisch typisierte Sprache ) keine Datentypdeklaration gibt . Die einzige Möglichkeit, die obige Methode zu definieren, istdef(a,b)

Mit der zweiten Option könnte es so aussehen, als könnten wir eine Methodenüberladung erreichen, aber wir können nicht. Angenommen, 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.

Ruby muss also eine Methode in der Methodensuchkette mit einem eindeutigen Namen beibehalten.

nkm
quelle
22
@ Jörg W Mittag Antwort, weit unten begraben, ist definitiv lesenswert.
user2398029
1
Und @Derek Ekins 'Antwort, die noch weiter vergraben ist, bietet eine Alternative
Cyril Duchon-Doris
Hinweis für zukünftige Rubinisten ... FWIW können Sie dies mit Verträgen tun gem egonschiele.github.io/contracts.ruby/#method-overloading
engineeringDave
214

"Überladen" ist ein Begriff, der in Ruby einfach keinen Sinn ergibt. Es ist im Grunde ein Synonym für „statischen Argument-basierten Versand“, aber Rubin nicht hat statische Dispatch überhaupt . 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 keinen statischen Versand von jeder Art , ob Argument-basierte oder auf andere Weise.

Wenn Sie nicht speziell nach Überladung fragen, sondern nach dynamischem argumentbasiertem Versand, lautet die Antwort: Weil Matz sie nicht implementiert hat. Weil sich sonst niemand die Mühe gemacht hat, es vorzuschlagen. Weil sich sonst niemand die Mühe gemacht hat, es umzusetzen.

Im Allgemeinen ist es sehr schwierig, einen dynamischen argumentbasierten Versand in einer Sprache mit optionalen Argumenten und Argumentlisten variabler Länge richtig zu machen und noch schwieriger , ihn verständlich zu halten. Selbst in Sprachen mit statischem argumentbasiertem Versand 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 werden soll.

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

Versuchen Sie dies jetzt mit dem dynamischen Versand, bei dem Sie die zusätzliche Zeitdimension haben, die Sie im Kopf behalten müssen.

Es gibt Sprachen, die dynamisch basierend auf allen Argumenten einer Prozedur versenden, im Gegensatz zu objektorientierten Sprachen, die nur mit dem "versteckten" nullten selfArgument versenden . Common Lisp sendet beispielsweise die dynamischen Typen und sogar die dynamischen Werte aller Argumente aus. Clojure sendet eine willkürliche Funktion aller Argumente (was übrigens extrem cool und extrem mächtig ist).

Ich kenne jedoch keine OO-Sprache mit dynamischem argumentbasiertem Versand. Martin Odersky sagte, dass er erwägen könnte , Scala einen argumentbasierten Versand hinzuzufügen, aber nur, wenn er gleichzeitig die Überladung entfernen und abwärtskompatibel sein kann, sowohl mit vorhandenem Scala-Code, der Überladung verwendet, als auch kompatibel mit Java (er erwähnte insbesondere Swing und AWT) die einige extrem komplexe Streiche spielen, die so ziemlich jeden bösen dunklen Eckfall von Javas ziemlich komplexen Überladungsregeln ausüben). Ich hatte selbst einige Ideen, wie ich Ruby einen argumentbasierten Versand hinzufügen kann, aber ich konnte nie herausfinden, wie ich es abwärtskompatibel machen kann.

Jörg W Mittag
quelle
5
Das ist die richtige Antwort. Die akzeptierte Antwort ist zu einfach. C # DOES hat benannte Parameter und optionale Parameter und implementiert immer noch das Überladen. Es ist also nicht so einfach wie "Funktioniert def method(a, b = true)nicht, daher ist das Überladen von Methoden nicht möglich." Es ist nicht; es ist nur schwierig. Ich fand diese Antwort jedoch sehr informativ.
Tandrewnichols
1
@tandrewnichols: Nur um eine Vorstellung davon zu geben, wie "schwierig" die Überlastungsauflösung in C # ist ... es ist möglich, jedes 3-SAT-Problem als Überlastungsauflösung in C # zu codieren und vom Compiler zur Kompilierungszeit lösen zu lassen, wodurch eine Überlastungsauflösung in C # NP erfolgt -hard (3-SAT ist bekanntermaßen NP-vollständig). Stellen Sie sich nun vor, Sie müssen dies nicht einmal pro Aufrufstandort zur Kompilierungszeit tun, sondern einmal pro Methodenaufruf für jeden einzelnen Methodenaufruf zur Laufzeit.
Jörg W Mittag
1
@ JörgWMittag Könnten Sie einen Link zur Codierung eines 3-SAT-Problems in den Überlastungsauflösungsmechanismus aufnehmen?
Tintenfisch
2
Es sieht nicht so aus, als ob "Überladen" ein Synonym für "statisches argumentbasiertes Versenden" ist. Statischer argumentbasierter Versand ist einfach die häufigste Implementierung von Überladung. Überladen ist ein implementierungsunabhängiger Begriff, der "gleichen Methodennamen, aber unterschiedliche Implementierungen im selben Bereich" bedeutet.
Snovity
85

Ich nehme an, Sie suchen nach der Möglichkeit, dies zu tun:

def my_method(arg1)
..
end

def my_method(arg1, arg2)
..
end

Ruby unterstützt dies auf andere Weise:

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

Ein gängiges Muster ist auch die Übergabe von Optionen als Hash:

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'

hoffentlich hilft das

Derek Ekins
quelle
15
+1 für die Bereitstellung dessen, was viele von uns nur wissen möchten: wie man mit einer variablen Anzahl von Argumenten in Ruby-Methoden arbeitet.
Asche999
3
Diese Antwort kann von zusätzlichen Informationen zu optionalen Argumenten profitieren. (Und vielleicht auch Argumente genannt, jetzt wo das eine Sache ist.)
Ajedi32
9

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

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

sowie zwischen unterschiedlicher Anzahl von Argumenten

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

Die erste Unterscheidung existiert nicht in Rubin. Ruby verwendet dynamisches Tippen oder "Enten-Tippen". Die zweite Unterscheidung kann durch Standardargumente oder durch Arbeiten mit Argumenten erfolgen:

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


def f(*args)
  case args.size
  when  
     ...
  when 2
    ...
  when 3
    ...
  end
end
bjelli
quelle
Dies hat nichts mit starker Eingabe zu tun. Immerhin ist Ruby stark getippt.
Jörg W Mittag
8

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

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

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 leistungsfähiger ist als die Überladung von Java, da Sie Werte angeben können, die übereinstimmen (z. B. 1), nicht nur Typen.

Sie werden jedoch eine verminderte Leistung feststellen, wenn Sie dies verwenden. Sie müssen Benchmarks durchführen, um zu entscheiden, wie viel Sie tolerieren können.

Kelvin
quelle
1
In realen Anwendungen mit jeder Art von E / A haben Sie nur eine Verlangsamung von 0,1-10% (abhängig von der Art der E / A).
Waterlink
1

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

Auf diese Weise kann der Benutzer des Objekts die saubere und klare Methode method_name: method verwenden. Wenn er jedoch die Ausführung optimieren möchte, kann er direkt die richtige Methode aufrufen.

Außerdem werden Ihre Tests klarer und besser.

Damm
quelle
1

Es gibt bereits gute Antworten auf die Warum-Seite der Frage. Wenn Sie jedoch nach anderen Lösungen suchen, schauen Sie sich das funktionale Rubinjuwel an, das von den Elixir- Mustervergleichsfunktionen inspiriert ist.

 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!"   
Oshan Wisumperuma
quelle