Warum sind Mehrfachrückgabewerte nicht üblich?


7

Ich möchte eine Frage zu mehreren Rückgabewerten stellen, warum dieses Konstrukt in Programmiersprachen (konzeptionelle und / oder technische Schwierigkeiten) nicht vorzuziehen ist. Ich habe etwas über Stapelrahmen gehört und wie sie Speicher für Rückgabewerte und variable Rückgabewerte reservieren, könnte dies problematisch machen, aber wenn jemand dies besser erklären könnte, wäre dies sehr dankbar.

In konktanetativen Sprachen (wie FORTH) ist es üblich und sehr nützlich, mehrere Rückgabewerte von einer Funktion zu haben, und ich stelle mir vor, dass so etwas in Java / C-ähnlichen Sprachen ebenfalls nützlich wäre (ein sehr einfaches Beispiel):

x, y = multRet(5);
multret(int x) {
    return x+1;
    return x+2;
    exit;
}

Zur Verdeutlichung: Ich frage nicht, wie mehrere Werte zurückgegeben werden sollen (dies ist eine bekannte Frage mit bekannten Antworten), aber ich möchte eine Klarstellung darüber erhalten, warum diese Praxis in Programmiersprachen nicht üblich ist.


4
Eine mögliche Erklärung ist, dass Sie, wenn Sie mehrere Werte zurückgeben müssen, dies immer tun können, indem Sie eine Struktur oder Liste zurückgeben. Warum die Sprache und den Compiler komplizieren?
David Richerby

4
Ich denke nicht, dass die Frage beantwortbar ist. Erstens ist dies nur eine Frage des Geschmacks. Zweitens denke ich, dass die Prämisse, dass es "ungewöhnlich" ist, falsch ist. Es gibt eine große Anzahl von Sprachen, die das Zurückgeben von Tupeln unterstützen, und zwar seit den 1970er Jahren, und die Praxis des Zurückgebens von zurückgegebenen Tupeln scheint an Popularität zu gewinnen, da alle kürzlich entwickelten populären Systemprogrammiersprachen (Swift, Go, Rust) das Zurückgeben von Tupeln unterstützen .
Wandering Logic

2
Ich stimme @DavidRicherby zu; Eine solche Funktion wäre redundant und hätte eine möglicherweise schwierige / zwielichtige Semantik (was passiert damit i=0; while (true) { return i; i+=1 }?). Beachten Sie, dass einige funktionale Sprachen Tupel anbieten, eine bessere Alternative.
Raphael

1
@WanderingLogic Ein Tupel ist immer noch ein Wert, der von einer returnAnweisung zurückgegeben wird. Wenn Sie Funktionen / Lambdas zurückgeben, haben Sie möglicherweise einen Punkt.
Raphael

1
Ich bin nicht der Meinung, dass diese Frage meinungsbasiert ist: Die bisherigen Kommentare weisen darauf hin, dass es technische Antworten gibt. Wenn Sie der Meinung sind, dass der "Warum" -Teil der Frage zu subjektiv ist, kann er leicht in "Was sind die Vor- und Nachteile des Zulassens mehrerer Rückgabewerte?" Bearbeitet werden.
David Richerby

Antworten:


7

Funktionen werden traditionell als Rückgabe eines einzelnen Wertes angesehen - in der Mathematik. Natürlich bilden mehrere Werte ein Tupel, aber in der Mathematik handelt es sich häufig um reelle Funktionen. Ich vermute, dass dieser Ursprung der Funktionen der Grund ist, warum Mehrfachrückgabewerte nicht so häufig sind. Das heißt, es ist einfach, sie auf verschiedene Arten zu emulieren (indem ein Tupel zurückgegeben wird oder indem mehrere Ausgabeparameter, dh solche, die als Referenz übergeben werden), und einige gängige moderne Sprachen (wie Python) unterstützen diese Konstruktion nativ.


4

Es ist vorzuziehen, mehrere Rückgabewerte zu haben . Ihnen zu fehlen ist schlechtes Design, schlicht und einfach. Wir sollten jedoch das Missverständnis beseitigen, dass so etwas sogar in dem Sinne existiert, an den Sie denken.

In Sprachen, die auf dem typisierten Lambda-Kalkül (den Vorläufer-Programmiersprachen) basieren, z. B. der ML-Familie Haskell, akzeptiert eine Funktion (Lambda-Abstraktion) nur ein einziges Argument und gibt ein einzelnes Ergebnis zurück (formeller ausgewertet). Wie können wir also eine Funktion erstellen, die mehrere Werte anzunehmen oder zurückzugeben scheint?

Die Antwort ist, dass wir Werte mithilfe von Produkttypen "zusammenkleben" können . Wenn A und B beide Typen sind, dann der ProdukttypEIN×B. enthält alle Tupel des Formulars (ein,b), wo ein::EIN und b::B..

multiplyUm eine Funktion zu erstellen , die zwei ganze Zahlen miteinander multipliziert, geben wir ihr den Typ multiply: int * int -> int. Es akzeptiert ein einzelnes Argument, das zufällig ein Tupel ist. Ebenso hätte eine Funktion split, die eine Liste in zwei Teile aufteilt, den Typ split: a list -> a list * a list.

Vielleicht war die Implementierung vor 30 Jahren ein berechtigtes Anliegen, eine solche Funktion aufzunehmen. Gemäß der C-Aufrufkonvention cedl (und es ist nur so, dass eine Konvention - kein Gott hat sie bestimmt) wird der Rückgabewert normalerweise in einem speziellen Register gespeichert EAX. Dies ist sicherlich extrem schnell zu lesen und zu schreiben, daher hat die Idee einige Vorteile.

Aber es ist zu einschränkend, wie Sie betonen, und es gibt keinen Grund, warum wir diese Regel heutzutage befolgen müssen. Zum Beispiel könnten wir stattdessen die folgende Richtlinie verwenden: Verwenden Sie für eine kleine Anzahl von Rückgabewerten eine Reihe angegebener Register (wir haben jetzt mehr Register zur Verwendung als in den 80er Jahren). Bei größeren Daten können wir einen Zeiger auf eine Stelle im Speicher zurückgeben. Ich weiß nicht, was am besten ist. Ich bin kein Compiler-Autor. Die archaische C-Aufrufkonvention sollte jedoch keinen Einfluss auf die Semantik moderner Sprachen haben.


1

Syntax ist nicht erforderlich, wenn einfache Muster die Arbeit besser erledigen und Ihre vorgeschlagene Syntax für viele Menschen historisch verwirrend wäre. Dies bedeutet, dass Ihre Syntax schwer zu erlernen ist und zu Problemen führt, wenn Personen zwischen Ihrer Sprache und anderen wechseln.

Das einfache Muster, das dieses Problem löst, besteht darin, zu Beginn Ihrer Methode eine Variable mit dem Namen "returnValue" zu definieren und ihr dann Werte zuzuweisen. Wenn Sie mehrere Werte benötigen, verwenden Sie einfach eine Sammlung und fügen Sie stattdessen die Werte hinzu. Geben Sie dann diese Variable zurück. Problem gelöst.

Ihre Syntax würde bedeuten, dass Sie standardmäßig Sammlungen verarbeiten müssten. Dies ist frustrierend für Funktionen, die nur 1 Wert zurückgeben.

Und was ist, wenn jemand etwas falsch macht? Z.B.

x, y = multRet(5);
multret(int x) {
    return x+1;
    exit;
}

oder

x = multRet(5);
multret(int x) {
    return x+1;
    return x+1;
    exit;
}

Soll der Compiler das fangen? Was ist, wenn Sie eine Schleife haben?

Bestenfalls wäre dies syntaktischer Zucker. Etwas, das keine neuen Funktionen hinzufügt, sondern nur aktuelle Muster komfortabler und kürzer im Code macht.

Im schlimmsten Fall würde dies zu einem schrecklichen, schrecklichen Codefehler führen. Nullzeigerausnahme in Massen.


1
Der Compiler kann problemlos mehrere Rückgabewerte abfangen. Das macht die Programmiersprache Go.
Slebetman

1

Es ist hauptsächlich aus syntaktischen Gründen; In Ihrem eigenen Beispiel würde die erste return-Anweisung einen Wert und return zurückgeben und niemals die zweite return-Anweisung erreichen.

Heutzutage gibt es einige Sprachen, in denen ein Tupel zurückgegeben werden kann, dh null, ein oder mehrere Werte beliebigen Typs, die zu einem einzigen Wert zusammengefasst sind. Trotzdem geben sie einen zurück Wert zurück, nur einen etwas komplizierteren Wert. Normalerweise haben solche Sprachen auch die Möglichkeit, ein Tupel Null, einer oder mehreren übereinstimmenden Variablen zuzuweisen, häufig mit einer einfachen Möglichkeit, einige Elemente des Tupels zu ignorieren. Das könnte so aussehen:

Weisen Sie einem anderen Tupel ein Tupel zu:

x = multRet(5); // x is a variable of type tuple (int, int)
multret(int x) {
    return (x+1, x+2);
}

(x, y) = multRet(5); // x, y are variables of type int
multret(int x) {
    return (x+1, x+2);
}

(x, _) = multRet(5); // x is a variable of type int, second component ignored. 
multret(int x) {
    return (x+1, x+2);
}

Diese Syntax wäre nicht kompatibel mit C, wobei (x, y) ein Kommaausdruck in Klammern ist. Andererseits ist das Empfangen von zwei Werten viel einfacher als in C, wo Sie entweder die beiden Werte mit Zeigern übergeben oder eine Struktur für diesen Zweck deklarieren, sie füllen und zurückgeben. Bei Verwendung von Zeigern ist das dritte Beispiel, bei dem der Aufrufer nur an einem Wert interessiert ist, ein Schmerz. Daher ist diese Methode zum Zurückgeben von Tupeln sehr nützlich.


Go ist ein Beispiel, bei dem die Rückgabewerte separate Werte anstelle eines Tupels sind. In Go können Sie das zweite und dritte Beispiel ausführen, aber das erste Beispiel würde zu einem Fehler bei der Kompilierung führen: Der Compiler erwartet zwei Variablen, erhält jedoch nur eine. Die von Go gewählte Syntax ist einfach return x, yund um die Rückgabewerte zu akzeptieren, die Sie tun würdenx,y = multiRet()
slebetman

Du meinst, Go hat keine Variablen vom Typ Tupel?
Gnasher729

Wie alle anderen C-ähnlichen Sprachen hat Go Strukturen und wie die meisten modernen Sprachen hat Go einen Kartentyp (Hash / assoziatives Array), sodass Sie Tupel entweder als Strukturen oder als Karten implementieren können. Eine Sache, die Go nicht oft sehe, sind anonyme Strukturen - Strukturen ohne Namen.
Slebetman

Nun, neuere Sprachen wie Swift haben Tupel. Wenn Sie Tupel selbst implementieren müssen, wird der größte Teil des Vorteils weggenommen.
Gnasher729

In LISP ist es üblich. Wir können auch die Mehrfachwertbindung verwenden, um jeden Rückgabewert einer Variablen zuzuweisen
Talk is Cheap Show me Code
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.