Wróć do Scali

84

Jestem początkującym programistą Scala i napotkałem dziwne zachowanie.

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

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

Powyżej w zasadzie chcę zwrócić prawdę, jeśli elem.isEmptyicount == 0 . W przeciwnym razie chcę zwrócić wartość false.

Powyżej przeczytałem, że nie ma potrzeby dodawania instrukcji return w scali. Więc pominąłem returnpowyżej. Ale nie zwraca wartości logicznej. Jeśli dodam instrukcję zwrotu jako return true. działa idealnie. Dlaczego tak się dzieje?

Ponadto, dlaczego stosowanie instrukcji powrotu w scali jest uważane za złą praktykę

Jatin
źródło
2
Jest zwykle nie ma potrzeby dla słowa kluczowego powrotnej, tak długo, jak złamać kod na tyle mały metod.
mauhiz
@mauhiz Thanks. Czy możesz to wyjaśnić? Jak to zrobisz.
Jatin
4
wygląda na to, że bierzesz udział w kursie Coursera Scala. wszystkiego najlepszego :)
weima

Odpowiedzi:

139

Nie jest to tak proste, jak po prostu pominięcie returnsłowa kluczowego. W Scali, jeśli nie ma, returnto ostatnie wyrażenie jest traktowane jako wartość zwracana. Jeśli więc chcesz zwrócić ostatnie wyrażenie, możesz pominąć returnsłowo kluczowe. Ale jeśli to, co chcesz zwrócić, nie jest ostatnim wyrażeniem, Scala nie będzie wiedział, że chcesz to zwrócić .

Przykład:

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

Tutaj ostatnim wyrażeniem funkcji fjest wyrażenie if / else, którego wynikiem jest String. Ponieważ nie ma wyraźnegoreturn zaznaczenia, Scala wywnioskuje, że chcesz zwrócić wynik tego wyrażenia if / else: String.

Teraz, jeśli dodamy coś po wyrażeniu if / else:

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

  if (somethingElse)
    1
  else
    2
}

Teraz ostatnim wyrażeniem jest wyrażenie if / else, którego wynikiem jest Int. Więc zwracanym typem fbędzie Int. Jeśli naprawdę chcieliśmy, aby zwrócił String, mamy kłopoty, ponieważ Scala nie ma pojęcia , że tak właśnie było. W związku z tym musimy to naprawić, albo przechowując ciąg znaków w zmiennej i zwracając go po drugim wyrażeniu if / else, albo zmieniając kolejność, aby część typu String występowała jako ostatnia.

Wreszcie, możemy uniknąć returnsłowa kluczowego nawet w przypadku zagnieżdżonego wyrażenia if-else, takiego jak twoje:

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
źródło
4
Na wszystkich bogów, dzięki! Godzinami walczyłem z tym samym problemem (również robiąc kurs w Coursera) i nie byłem w stanie zrozumieć, dlaczego powrót jest wymagany.
Igor Rodriguez
w pierwszym przykładzie, co się stanie, jeśli dodasz zwroty. ie return "A"i return "B"?
SamAko
@ T.Rex zwróci to wywołującemu funkcję f () z wartością „A” lub „B”. Ale jak wyjaśniłem w mojej odpowiedzi. Nigdy nie używaj zwrotu w Scali. Jeśli instrukcje w Scali działają w sposób funkcjonalny. Oceniają coś z typem. Podobnie jak operator trójskładnikowy w Javie (? :). Przykład: val foo = if (mybool) „A” else „B” - foo będzie łańcuchem zawierającym „A” lub „B”. Pomyśl też o funkcji, która nie zwraca czegoś, ale zwraca wartość ostatniego wyrażenia w niej.
Grmpfhmbl,
23

Ten temat jest właściwie trochę bardziej skomplikowany, niż opisano w dotychczasowych odpowiedziach. Ten post na blogu Roba Norrisa wyjaśnia to bardziej szczegółowo i podaje przykłady, kiedy użycie return faktycznie zepsuje twój kod (lub przynajmniej spowoduje nieoczywiste efekty).

W tym miejscu pozwolę sobie tylko zacytować istotę postu. Najważniejsze stwierdzenie znajduje się zaraz na początku. Wydrukuj to jako plakat i umieść na ścianie :-)

Słowo returnkluczowe nie jest „opcjonalne” ani „wywnioskowane”; zmienia znaczenie twojego programu i nigdy nie powinieneś go używać.

Podaje jeden przykład, w którym faktycznie coś psuje, gdy wstawiasz funkcję

// 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.

dlatego

Po returnoszacowaniu wyrażenie porzuca bieżące obliczenia i wraca do obiektu wywołującego metodę, w której się returnpojawia.

To tylko jeden z przykładów podanych w linkowanym poście i jest on najłatwiejszy do zrozumienia. Jest ich więcej i gorąco zachęcam do pójścia tam, przeczytania i zrozumienia.

Jeśli pochodzisz z imperatywnych języków, takich jak Java, może to początkowo wydawać się dziwne, ale gdy przyzwyczaisz się do tego stylu, nabierze sensu. Zakończę kolejnym cytatem:

Jeśli znajdziesz się w sytuacji, w której myślisz, że chcesz wrócić wcześniej, musisz ponownie przemyśleć sposób, w jaki zdefiniowałeś swoje obliczenia.

Grmpfhmbl
źródło
12
Nie zgadzam się z Robem, IMO nie wolno używać returntylko w wyrażeniach lambda, ale jest to gówniany projekt w języku, kod nie powinien się nawet kompilować (w Pythonie można tego uniknąć, mając lambdasłowo kluczowe bez instrukcji return). .. w każdym innym przypadku nie widzę prawdziwego problemu w używaniu, returnjeśli chcesz zwrócić wartość (i tak, wyjść z wykonywania metody, ponieważ jest to, do czego używany jest zwrot we wszystkich językach!)
daveoncode
2
Masz swobodę wyrażania opinii, ale wiele osób zgadza się, że używanie zwrotu w Scali to co najmniej zły styl. Ośmielam się znaleźć oficjalne przykłady dokumentów Scala, które używają zwrotu. AFAIK nie jest nawet wyjaśniony w oficjalnych dokumentach, tylko w dokumencie specyfikacji (rozdz. 6.20). Cytując samego Martina Odersky'ego (Programowanie w Scali) „Zalecanym stylem dla metod jest w rzeczywistości unikanie jawnych, a zwłaszcza wielokrotnych instrukcji zwracających. Zamiast tego myśl o każdej metodzie jako o wyrażeniu, które zwraca jedną wartość, która jest zwracana”. Pierwsze wydanie tej książki jest dostępne bezpłatnie online artima.com/pins1ed
Grmpfhmbl
4

Nie programuję Scali, ale używam innego języka z niejawnymi zwrotami (Ruby). Masz kod po swoim if (elem.isEmpty)bloku - ostatnia linia kodu jest zwracana, dlatego nie otrzymujesz tego, czego oczekujesz.

EDYCJA: Tutaj jest również prostszy sposób pisania funkcji. Po prostu użyj wartości logicznej isEmpty i count, aby automatycznie zwrócić prawdę lub fałsz:

def balanceMain(elem: List[Char]): Boolean =
{
    elem.isEmpty && count == 0
}
jmdeldin
źródło
Dzięki. Ale chcę powrócić tylko wtedy, gdy elem.isEmpty && count == 0 return true w przeciwnym razie kontynuuj blok. Powyższe powróci, nawet jeśli jest fałszywe.
Jatin
@Jatin: Ach, nie zdawałem sobie z tego sprawy. W explicittakim przypadku wcześniejszy i zwrot byłby odpowiedni.
jmdeldin
@Frank: Nie jest też zdefiniowane w kodzie OP. Zakładałem, że to wywołanie metody.
jmdeldin
4

Nie pisz ifoświadczeń bez odpowiedniego else. Po dodaniu elsedo swojego fragmentu zobaczysz, że twoje truei falsefaktycznie są ostatnimi wyrażeniami funkcji.

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
źródło
6
Zastosowałem to, uzyskałem 3 zagnieżdżone IF i wygląda brzydko. Czy jest jakiś wzór, aby wyglądał ładniej?
Capacytron
4

Domyślnie zostanie zwrócone ostatnie wyrażenie funkcji. W twoim przykładzie jest inne wyrażenie po punkcie, w którym chcesz zwrócić wartość. Jeśli chcesz zwrócić cokolwiek przed ostatnim wyrażeniem, nadal musisz użyć return.

Możesz zmodyfikować swój przykład w ten sposób, aby zwrócić Booleanz pierwszej części

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
źródło
9
Nie „oświadczenie”. „ekspresja”
Viktor Klang,
0

Dopasowanie przypadków użycia do celów wczesnego zwrotu. Zmusi cię to do jawnego zadeklarowania wszystkich gałęzi powrotu, zapobiegając nieostrożnemu błędowi zapomnienia gdzieś o zapisaniu powrotu.

Zhu Jinxuan
źródło