Dlaczego Scala wymaga, aby funkcje miały jawny typ zwrotu?

11

Niedawno zacząłem uczyć się programowania w Scali i jak dotąd było fajnie. Naprawdę podoba mi się możliwość deklarowania funkcji w ramach innej funkcji, co wydaje się intuicyjne.

Jedno wkurzenie, jakie mam o Scali, to fakt, że Scala wymaga wyraźnego typu zwrotu w swoich funkcjach . I czuję, że przeszkadza to w ekspresji języka. Trudno też zaprogramować ten wymóg. Może dlatego, że pochodzę ze Javascript i Ruby comfort zone. Ale dla języka takiego jak Scala, który będzie miał mnóstwo połączonych funkcji w aplikacji, nie mogę sobie wyobrazić, jak burzę mózgów w mojej głowie dokładnie, jaki typ konkretnej funkcji, którą piszę, powinien powrócić z rekurencjami po rekurencjach.

Ten wymóg wyraźnej deklaracji typu zwrotu dla funkcji nie przeszkadza mi w przypadku języków takich jak Java i C ++. Rekurencje w Javie i C ++, kiedy się zdarzały, często zajmowały maksymalnie 2 do 3 funkcji. Nigdy nie połączono ze sobą kilku funkcji, takich jak Scala.

Myślę, że zastanawiam się, czy istnieje dobry powód, dla którego Scala powinna mieć wymóg, aby funkcje miały jawny typ zwrotu?

zbieranie śmieci
źródło
5
Nie robi tego - i nie rozumiem, dlaczego miałby to być problem.
Keith Thompson
1
Myślałem, że tak. Czy w Scali są przypadki, w których typ zwracany przez funkcję jest w rzeczywistości niejednoznaczny?
wywóz śmieci

Odpowiedzi:

15

Scala nie wymaga jawnego typu zwrotu dla wszystkich funkcji, tylko te rekurencyjne. Powodem tego jest to, że algorytm wnioskowania typu Scali jest (coś bliskiego) prostym skanem od początku do końca, który nie jest w stanie spojrzeć w przyszłość.

Oznacza to, że taka funkcja:

def fortuneCookieJoke(message: String) = message + " in bed."

nie potrzebuje typu zwracanego, ponieważ kompilator Scala może wyraźnie zobaczyć, bez użycia zmiennych logicznych lub patrzenia na nic innego niż parametry metody, że musi to być typ zwracany String.

Z drugiej strony taka funkcja:

def mapInts(f: (Int) => Int, l: List[Int]) = l match {
  case Nil => Nil
  case x :: xs => f(x) :: mapInts(f, xs)
}

spowoduje błąd czasu kompilacji, ponieważ kompilator Scala nie widzi, bez użycia zmiennych typu lookahead lub zmiennych logicznych, dokładnie tego, jaki jest typ mapInts. Gdyby był wystarczająco sprytny, mógłby powiedzieć, że typ zwracany jest nadtypem List[Nothing], ponieważ Niljest tego typu. Nie daje to nigdzie blisko informacji wystarczających do dokładnego określenia rodzaju zwrotu mapInts.

Należy pamiętać, że jest to specyficzne dla Scali i że istnieją inne języki o typie statycznym (większość rodziny Miranda / Haskell / Clean, większość rodziny ML i kilka innych rozproszonych), które używają znacznie bardziej kompleksowych i zdolnych algorytmów wnioskowania typu niż używa Scala. Pamiętaj też, że nie jest to całkowicie wina Scali; nominalne podtypy i wnioskowanie o typach modułów są zasadniczo sprzeczne ze sobą, a projektanci Scali zdecydowali się faworyzować te pierwsze ze względu na kompatybilność z Javą, podczas gdy „czystsze” statyczne języki funkcjonalne zostały zaprojektowane głównie z przeciwny wybór.

Płomień Pthariena
źródło
4
W rzeczywistości problemem jest nie tyle ustalenie bardziej kompleksowego algorytmu wnioskowania. Opracowuje bardziej kompleksowy algorytm wnioskowania, zachowując jednocześnie wysoką jakość komunikatów o błędach w bieżącym kompilatorze Scala.
Jörg W Mittag
1
Czy poprawny zwrot case Nilnie byłby rzeczywiście pusty List[Int]()? W takim przypadku wystarczająco inteligentny kompilator mógłby to rozgryźć. To chyba wszystko w roli Diabelskiego Adwokata.
KChaloux, 12.12.12