Rückkehr in die Scala

80

Ich bin ein Neuling Scala-Programmierer und stieß auf ein seltsames Verhalten.

def balanceMain(elem: List[Char]): Boolean =
  {
    if (elem.isEmpty)
      if (count == 0)
        true;
      else false;

    if (elem.head == '(')
      balanceMain(elem.tail, open, count + 1);....

Oben möchte ich grundsätzlich true zurückgeben, wenn elem.isEmptyund count == 0. Ansonsten möchte ich false zurückgeben.

Jetzt oben habe ich gelesen, dass es nicht notwendig ist, eine return-Anweisung in scala hinzuzufügen. Also habe ich returnoben weggelassen . Der boolesche Wert wird jedoch nicht zurückgegeben. Wenn ich eine return-Anweisung als hinzufüge return true. es funktioniert perfekt. Wieso ist es so?

Warum wird es auch als schlechte Praxis angesehen, Rückgabeanweisungen in Scala zu haben?

Jatin
quelle
2
Das Schlüsselwort return ist normalerweise nicht erforderlich, solange Sie Ihren Code in ausreichend kleine Methoden aufteilen.
Mauhiz
@ Mauhiz Danke. Kannst du es bitte erklären? Wie wirst du es machen.
Jatin
4
Sieht aus wie Sie den Coursera Scala Kurs nehmen. Alles Gute :)
Weima

Antworten:

136

Es ist nicht so einfach, nur das returnSchlüsselwort wegzulassen . Wenn in Scala keine vorhanden ist, returnwird der letzte Ausdruck als Rückgabewert verwendet. Wenn Sie also den letzten Ausdruck zurückgeben möchten, können Sie das returnSchlüsselwort weglassen . Wenn das, was Sie zurückgeben möchten, nicht der letzte Ausdruck ist, weiß Scala nicht, dass Sie ihn zurückgeben möchten .

Ein Beispiel:

def f() = {
  if (something)
    "A"
  else
    "B"
}

Hier ist der letzte Ausdruck der Funktion fein if / else-Ausdruck, der zu einem String ausgewertet wird. Da keine explizite returnMarkierung vorhanden ist, schließt Scala, dass Sie das Ergebnis dieses if / else-Ausdrucks zurückgeben möchten: einen String.

Wenn wir nun nach dem if / else-Ausdruck etwas hinzufügen :

def f() = {
  if (something)
    "A"
  else
    "B"

  if (somethingElse)
    1
  else
    2
}

Jetzt ist der letzte Ausdruck ein if / else-Ausdruck, der zu einem Int ausgewertet wird. Der Rückgabetyp von fwird also Int sein. Wenn wir wirklich wollten, dass der String zurückgegeben wird, sind wir in Schwierigkeiten, weil Scala keine Ahnung hat , dass wir das beabsichtigt haben. Daher müssen wir das Problem beheben, indem wir entweder den String in einer Variablen speichern und nach dem zweiten if / else-Ausdruck zurückgeben oder die Reihenfolge so ändern, dass der String-Teil zuletzt auftritt.

Schließlich können wir das returnSchlüsselwort auch mit einem verschachtelten if-else-Ausdruck wie Ihrem vermeiden :

def f() = {
  if(somethingFirst) {
    if (something)      // Last expression of `if` returns a String
     "A"
    else
     "B"
  }
  else {
    if (somethingElse)
      1
    else
      2

    "C"                // Last expression of `else` returns a String
  }

}}

dhg
quelle
3
Bei allen Göttern, danke! Ich kämpfte stundenlang mit dem gleichen Problem (machte auch den Kurs in Coursera) und konnte nicht verstehen, warum die Rückkehr erforderlich war.
Igor Rodriguez
Was passiert im ersten Beispiel, wenn Sie Retouren hinzufügen? dh return "A"und return "B"?
SamAko
@ T.Rex würde mit dem Wert "A" oder "B" zum Aufrufer von f () zurückkehren. Aber wie ich in meiner Antwort erklärt habe. Verwenden Sie niemals return in Scala. Wenn Anweisungen in Scala funktional funktionieren. Sie bewerten etwas mit einem Typ. Wie der ternäre Operator in Java (? :). Beispiel: val foo = if (mybool) "A" else "B" - foo ist ein String, der entweder "A" oder "B" enthält. Stellen Sie sich ebenfalls eine Funktion vor, die nicht etwas zurückgibt, sondern den Wert des letzten Ausdrucks darin bewertet.
Grmpfhmbl
23

Dieses Thema ist tatsächlich etwas komplizierter, wie in den bisherigen Antworten beschrieben. Dieser Blogpost von Rob Norris erklärt es ausführlicher und gibt Beispiele dafür, wann die Verwendung von return Ihren Code tatsächlich beschädigt (oder zumindest nicht offensichtliche Auswirkungen hat).

Lassen Sie mich an dieser Stelle nur die Essenz des Beitrags zitieren. Die wichtigste Aussage ist gleich am Anfang. Drucken Sie dies als Poster aus und hängen Sie es an Ihre Wand :-)

Das returnSchlüsselwort ist nicht "optional" oder "abgeleitet". Es ändert die Bedeutung Ihres Programms und Sie sollten es niemals verwenden.

Es gibt ein Beispiel, wo es tatsächlich etwas kaputt macht, wenn Sie eine Funktion inline

// Inline add and addR
def sum(ns: Int*): Int = ns.foldLeft(0)((n, m) => n + m) // inlined add

scala> sum(33, 42, 99)
res2: Int = 174 // alright

def sumR(ns: Int*): Int = ns.foldLeft(0)((n, m) => return n + m) // inlined addR

scala> sumR(33, 42, 99)
res3: Int = 33 // um.

weil

returnWenn ein Ausdruck ausgewertet wird, gibt er die aktuelle Berechnung auf und kehrt zum Aufrufer der Methode zurück, in der er returnangezeigt wird.

Dies ist nur eines der Beispiele im verlinkten Beitrag und am einfachsten zu verstehen. Es gibt noch mehr und ich ermutige Sie sehr, dorthin zu gehen, zu lesen und zu verstehen.

Wenn Sie aus imperativen Sprachen wie Java kommen, mag dies zunächst seltsam erscheinen, aber sobald Sie sich an diesen Stil gewöhnt haben, ist dies sinnvoll. Lassen Sie mich mit einem anderen Zitat schließen:

Wenn Sie sich in einer Situation befinden, in der Sie glauben, frühzeitig zurückkehren zu wollen, müssen Sie die Art und Weise, wie Sie Ihre Berechnung definiert haben, überdenken.

Grmpfhmbl
quelle
10
Ich stimme Rob nicht zu, IMO darfst du nicht returnnur in Lambda-Ausdrücken verwenden, aber dies ist eine beschissene Design-Wahl in der Sprache, der Code sollte nicht einmal kompiliert werden (in Python wird dies vermieden, indem ein lambdaSchlüsselwort ohne die return-Anweisung vorhanden ist). .. in jedem anderen Fall sehe ich kein wirkliches Problem bei der Verwendung, returnwenn Sie einen Wert zurückgeben möchten (und ja,
beenden
2
Es steht Ihnen frei, Ihre Meinung zu äußern, aber viele Menschen sind sich einig, dass die Verwendung von Return in Scala zumindest ein schlechter Stil ist. Ich wage Sie, offizielle Beispiele für Scala-Dokumente zu finden, die return verwenden. Es ist AFAIK nicht einmal in den offiziellen Dokumenten erklärt, nur im Spezifikationsdokument (Kap. 6.20). Um Martin Odersky selbst zu zitieren (Programmieren in Scala): "Der empfohlene Stil für Methoden besteht darin, explizite und insbesondere mehrere return-Anweisungen zu vermeiden. Stellen Sie sich stattdessen jede Methode als Ausdruck vor, der einen Wert ergibt, der zurückgegeben wird." Die erste Ausgabe dieses Buches ist kostenlos online verfügbar. Artima.com/pins1ed
Grmpfhmbl
4

Ich programmiere Scala nicht, aber ich verwende eine andere Sprache mit impliziten Rückgaben (Ruby). Sie haben Code nach Ihrem if (elem.isEmpty)Block - die letzte Codezeile ist die zurückgegebene, weshalb Sie nicht das bekommen, was Sie erwarten.

BEARBEITEN: Hier ist eine einfachere Möglichkeit, Ihre Funktion zu schreiben. Verwenden Sie einfach den booleschen Wert von isEmpty und zählen Sie, um automatisch true oder false zurückzugeben:

def balanceMain(elem: List[Char]): Boolean =
{
    elem.isEmpty && count == 0
}
jmdeldin
quelle
Vielen Dank. Aber ich möchte nur zurückkehren, wenn elem.isEmpty && count == 0 true zurückgibt, sonst setze den Block fort. Das Obige wird zurückgegeben, auch wenn es falsch ist.
Jatin
@Jatin: Ah, das habe ich nicht bemerkt. Vorzeitige und explicitRückkehr wäre dann in diesem Fall angebracht.
jmdeldin
@Frank: Es ist auch nicht im OP-Code definiert. Ich nahm an, dass es ein Methodenaufruf war.
jmdeldin
4

Schreiben Sie keine ifAussagen ohne entsprechende else. Sobald Sie das elsezu Ihrem Fragment hinzufügen , werden Sie sehen, dass Ihre trueund falsetatsächlich die letzten Ausdrücke der Funktion sind.

def balanceMain(elem: List[Char]): Boolean =
  {
    if (elem.isEmpty)
      if (count == 0)
        true
      else
        false
    else
      if (elem.head == '(')
        balanceMain(elem.tail, open, count + 1)
      else....
Bart Schuller
quelle
5
Ich habe es angewendet, 3 verschachtelte IF bekommen und es sieht hässlich aus. Gibt es ein Muster, das es schöner aussehen lässt?
Capacytron
4

Standardmäßig wird der letzte Ausdruck einer Funktion zurückgegeben. In Ihrem Beispiel gibt es einen weiteren Ausdruck nach dem Punkt, an dem Sie Ihren Rückgabewert möchten. Wenn Sie vor Ihrem letzten Ausdruck etwas zurückgeben möchten, müssen Sie noch verwenden return.

Sie können Ihr Beispiel folgendermaßen ändern, um a Booleanaus dem ersten Teil zurückzugeben

def balanceMain(elem: List[Char]): Boolean = {
  if (elem.isEmpty) {
    // == is a Boolean resulting function as well, so your can write it this way
    count == 0
  } else {
    // keep the rest in this block, the last value will be returned as well
    if (elem.head == "(") {
      balanceMain(elem.tail, open, count + 1)
    }
    // some more statements
    ...
    // just don't forget your Boolean in the end
    someBoolExpression
  }
}
Tharabas
quelle
9
Keine "Aussage". "Ausdruck"
Viktor Klang
0

Use-Case-Match für Zwecke der vorzeitigen Rücksendung. Sie werden gezwungen, alle Rückgabezweige explizit zu deklarieren, um den unachtsamen Fehler zu vermeiden, dass Sie vergessen, irgendwo eine Rückgabe zu schreiben.

Zhu Jinxuan
quelle