Dlaczego typ występuje po nazwie zmiennej we współczesnych językach programowania?

57

Dlaczego w prawie wszystkich współczesnych językach programowania (Go, Rust, Kotlin, Swift, Scala, Nim, a nawet ostatnia wersja Python) typy zawsze pojawiają się po nazwie zmiennej w deklaracji zmiennej, a nie wcześniej?

Dlaczego x: int = 42nie int x = 42?
Czy ten drugi nie jest bardziej czytelny niż ten pierwszy?
Czy to tylko trend, czy istnieją naprawdę istotne powody tego rozwiązania?

Andre Polykanine
źródło
3
Tak, to dupek w porządku. Chociaż odnosi się konkretnie do języka Kotlin, pytanie (i odpowiedzi) dotyczy wszystkich języków posiadających tę praktykę.
Robert Harvey
2
Przepraszam chłopaki, widziałem pytanie o Kotlinie podczas googlowania, ale ponownie poprosiłem o to, aby nie otrzymywać odpowiedzi typu „Ponieważ zespół deweloperów Kotlin (Go, Rust, ...) tak postanowił”). Byłem zainteresowany bardziej popularną odpowiedzią: dlaczego to staje się rodzajem epidemii?
Andre Polykanine,
2
Cześć, więc Delphi, czyli (Object) Pascal, i ma typy po nazwach zmiennych od samego początku, dawno temu, gdy w ciemnych wiekach poprzedniego stulecia, znów jest nowoczesna;) Btw, na czytelność ma duży wpływ to, kim jesteś przyzwyczajony. Pochodzący z Delphi, C # ze swoim typem przed nazwą var, kazał mi walić głową od dłuższego czasu.
Marjan Venema,

Odpowiedzi:

64

Wszystkie wymienione języki obsługują wnioskowanie o typie , co oznacza, że typ jest opcjonalną częścią deklaracji w tych językach, ponieważ są one wystarczająco inteligentne, aby wypełnić je same, gdy podasz wyrażenie inicjujące o łatwym do określenia typie.

Ma to znaczenie, ponieważ przesunięcie opcjonalnych części wyrażenia dalej w prawo zmniejsza dwuznaczność analizowania i zwiększa spójność między wyrażeniami używającymi tej części a tymi, które jej nie używają. Po prostu łatwiej jest parsować deklarację, gdy wiadomo, że varsłowo kluczowe i nazwa zmiennej są obowiązkowe przed przejściem do opcjonalnych elementów. Teoretycznie wszystkie te rzeczy, które ułatwiają parsowanie komputerów, powinny poprawić ogólną czytelność również dla ludzi, ale jest to o wiele bardziej dyskusyjne.

Ten argument pobiera szczególnie silna jeśli wziąć pod uwagę wszystkie ewentualne modyfikatory typu, że „non-nowoczesne” język jak C ++ ma, takie jak *dla wskaźników, &dla referencji const, volatilei tak dalej. Kiedy wrzucisz przecinki dla wielu deklaracji, zaczynasz odczuwać naprawdę dziwne dwuznaczności, takie jak int* a, b;nie tworzenie bwskaźnika.

Nawet C ++ obsługuje teraz deklaracje „wpisz po prawej” w formie auto x = int{ 4 };i ma pewne zalety .

Ixrec
źródło
2
+1 za potwierdzenie istnienia obowiązkowych słów kluczowych varzapewnia większość uproszczenia analizy.
8bittree 20.04.2016
2
Czy istnienie varsłowa kluczowego nie jest sprzeczne z celem notacji „pierwsze imię”? Nie widzę tę zaletę, pisząc var userInput: String = getUserInput()na String userInput = getUserInput(). Korzyść byłaby istotna tylko wtedy, gdy userInput = getUserInput()jest dozwolona, ​​ale oznaczałoby to niejawną deklarację zmiennej o zasięgu, co wydaje mi się dość odrażające.
kdb
2
@kdb w językach takich jak C # i C ++ to byłoby var userInput = getUserInput()lub auto userInput = getUserInput(), kompilator zna getUserInput()zwroty, Stringwięc nie musisz go określać słowem kluczowym dedukcji typu. userInput = getUserInput()jest oczywiście używany przed deklaracją.
Wes Toleman
32

Dlaczego w prawie wszystkich współczesnych językach programowania (Go, Rust, Kotlin, Swift, Scala, Nim, a nawet ostatnia wersja Python) typy zawsze pojawiają się po deklaracji zmiennej, a nie wcześniej?

Twoje założenie jest wadliwe na dwóch frontach:

  • Istnieją nowe języki programowania, które mają typ przed identyfikatorem. Na przykład C♯, D lub Ceylon.
  • Mając typ po identyfikatorze, nie jest nowym zjawiskiem, wraca do przynajmniej Pascala (zaprojektowany 1968–1969, wydany w 1970 r.), Ale w rzeczywistości został użyty w matematycznej teorii typów, która zaczyna się około 1902 r. Był również używany w ML (1973), CLU (1974), Hope (1970s), Modula-2 (1977–1985), Ada (1980), Miranda (1985), Caml (1985), Eiffel (1985), Oberon (1986), Modula-3 (1986–1988) i Haskell (1989).

Pascal, ML, CLU, Modula-2 i Miranda były bardzo wpływowymi językami, więc nic dziwnego, że ten styl deklaracji typu pozostał popularny.

Dlaczego x: int = 42nie int x = 42? Czy ten drugi nie jest bardziej czytelny niż ten pierwszy?

Czytelność to kwestia znajomości. Osobiście uważam, że Chińczycy są nieczytelni, ale najwyraźniej Chińczycy nie. Kiedy nauczyłem się Pascala w szkole, bawiąc się Eiffelem, F♯, Haskellem i Scalą, i patrząc na TypeScript, Fortress, Go, Rust, Kotlin, Idris, Frege, Agda, ML, Ocaml,… wydaje mi się to całkowicie naturalne .

Czy to tylko trend, czy istnieją naprawdę istotne powody takiego rozwiązania?

Jeśli jest to trend, jest dość trwały: jak wspomniałem, matematyka sięga stu lat wstecz.

Jedną z głównych zalet posiadania typu po identyfikatorze jest to, że łatwo jest pominąć typ, jeśli chcesz go wywnioskować. Jeśli twoje deklaracje wyglądają tak:

val i: Int = 10

W takim razie pominięcie tego typu i wyciągnięcie z niego wniosku jest takie proste:

val i = 10

Natomiast jeśli typ występuje przed takim identyfikatorem:

Int i = 10

Następnie parser zaczyna mieć trudności z odróżnieniem wyrażenia od deklaracji:

i = 10

Rozwiązaniem, które zwykle wymyślają projektanci języków, jest wprowadzenie słowa kluczowego „Nie chcę pisać typu”, które należy wpisać zamiast typu:

var  i = 10; // C♯
auto i = 10; // C++

Ale to nie ma większego sensu: po prostu musisz wyraźnie napisać typ, który mówi, że nie piszesz typu. Co? Byłoby o wiele łatwiejsze i rozsądniejsze po prostu pominięcie tego, ale to czyni gramatykę znacznie bardziej złożoną.

(I nie mówmy nawet o typach wskaźników funkcji w C.)

Projektanci kilku wyżej wymienionych języków zastanawiali się nad tym tematem:

  • Często zadawane pytania (zobacz także: Składnia deklaracji Go ):

    Dlaczego deklaracje są cofane?

    Są one odwrócone tylko wtedy, gdy przyzwyczaiłeś się do C. W C chodzi o to, że zmienna jest zadeklarowana jak wyrażenie oznaczające jej typ, co jest fajnym pomysłem, ale gramatyka typów i wyrażeń nie łączy się zbyt dobrze i wyniki mogą być mylące; rozważ wskaźniki funkcji. Go głównie rozdziela składnię wyrażeń i typów, co upraszcza rzeczy (użycie przedrostka *dla wskaźników jest wyjątkiem potwierdzającym regułę). W C deklaracja

    int* a, b;
    

    deklaruje, aże jest wskaźnikiem, ale nie b; w Go

    var a, b *int
    

    deklaruje, że oba są wskaźnikami. Jest to wyraźniejsze i bardziej regularne. Również :=krótki formularz zgłoszenia twierdzi, że pełna deklaracja zmiennej powinna przedstawić takiej samej kolejności jak :=tak

    var a uint64 = 1
    

    ma taki sam efekt jak

    a := uint64(1)
    

    Przetwarzanie jest również uproszczone dzięki odrębnej gramatyce dla typów, które nie są tylko gramatyką wyrażeń; słowa kluczowe takie jak funci chantrzymaj wszystko jasne.

  • FAQ Kotlin :

    Po co deklaracje typu po prawej stronie?

    Uważamy, że dzięki temu kod jest bardziej czytelny. Poza tym umożliwia kilka fajnych funkcji składniowych. Na przykład łatwo jest pominąć adnotacje typu. Scala udowodniła też całkiem dobrze, że to nie jest problem.

  • Programowanie w Scali :

    Główne odchylenie od Javy dotyczy składni adnotacji typu - w Javie jest to „ variable: Type” zamiast „ Type variable”. Składnia typu Postala Scali przypomina Pascal, Modula-2 lub Eiffel. Głównym powodem tego odchylenia jest wnioskowanie o typie, które często pozwala pominąć typ zmiennej lub typ zwracanej metody. Przy użyciu variable: Typeskładni „ ” jest to łatwe - pomiń dwukropek i typ. Ale w Type variableskładni w stylu „C ” nie można po prostu pominąć tego typu - nie byłoby już markera, aby rozpocząć definicję. Potrzebujesz alternatywnego słowa kluczowego, aby być symbolem zastępczym dla brakującego typu (C♯ 3.0, który dokonuje wnioskowania typu, używa vardo tego celu). Takie alternatywne słowo kluczowe wydaje się bardziej ad hoc i mniej regularne niż podejście Scali.

Uwaga: projektanci Ceylonu również udokumentowali, dlaczego używają składni typu prefiksu :

Prefiks zamiast adnotacji typu postfiks

Dlaczego postępujesz zgodnie z C i Javą, umieszczając adnotacje na pierwszym miejscu, zamiast Pascal i ML, umieszczając je po nazwie deklaracji?

Ponieważ uważamy, że:

shared Float e = ....
shared Float log(Float b, Float x) { ... }

Jest po prostu o wiele łatwiejszy do odczytania niż to:

shared value e: Float = .... 
shared function log(b: Float, x: Float): Float { ... }

Po prostu nie rozumiemy, jak ktokolwiek mógłby myśleć inaczej!

Osobiście uważam, że ich „argument” jest o wiele mniej przekonujący niż inni.

Jörg W Mittag
źródło
4
Wydaje się, że zdolność opuścić typ i jeszcze proste parsowanie jest przyznawana przez dodanie var, auto, let, lub tym podobne, a nie za pomocą których strony deklaracja zmiennej typu jest włączony. var Int x = 5lub nawet Int var x = 5można je skrócić var x = 5.
8bittree 20.04.2016
5
Chociaż uważam, że jest równie czytelny, gdy się przyzwyczaisz, a opcjonalny argument typu jest silny, chciałbym zauważyć, że IDE nie jest w stanie automatycznie proponować nazw zmiennych na podstawie typu. Tęsknię za tym, kiedy piszę TypeScript.
Pierre Henry
"Your premise is flawed on two fronts" Wszystkie są nowsze niż C # lub D.
Det
3
„Ale to nie ma większego sensu: po prostu musisz wprost napisać typ, który mówi, że nie piszesz”. Cóż, wersja z właściwym tekstem jest dokładnie taka sama w twoim przykładzie ... Nie zgadzam się również, że to nie ma sensu. Ma to dokładnie taki sam sens jak funcw Go.
Sopel
4

Nawiasem mówiąc, Pascal też to robi i nie jest nowym językiem. Był to jednak język akademicki, zaprojektowany od podstaw.

Powiedziałbym, że semantycznie jaśniej jest zacząć od nazwy zmiennej. Ten typ jest tylko szczegółem technicznym. Jeśli chcesz odczytać swoją klasę jak model rzeczywistości, sensowne jest umieszczenie nazw swoich bytów na pierwszym miejscu, a ich technicznej implementacji na końcu.

C # i Java wywodzą się z C, więc musieli szanować „stan techniki”, aby nie mylić doświadczonego programisty.

Martin Maat
źródło
3
Ada robi to w ten sposób, ale z pewnością nie jest to „język akademicki”.
John R. Strohm,
Ada zrobiła to, ponieważ mocno wycięła z Pascala i Moduli 2.
Gort the Robot
2
@StevenBurnap, szybkie wyszukiwanie ujawnia, że ​​pierwsza książka Wirth na temat Moduli-2 ukazała się w połowie 1982 roku. Ada była opracowywana kilka lat wcześniej (DoD1 był w 1977 roku, jak pamiętam) i ustandaryzowała jako standard ANSI i MIL-STD w 1983 roku. Wszystkie cztery zespoły, które zgłosiły kandydatów na Adę, wzięły PASCAL jako ich punkty wyjścia. (DoD poprosił firmę Bell Labs o przesłanie kandydującego języka opartego na języku C. Odmówili, twierdząc, że C nie był wtedy i nigdy nie będzie wystarczająco odporny na krytyczne oprogramowanie DoD.)
John R. Strohm
Muszę źle pamiętać. Myślałem, że Wirth był zamieszany w Adę.
Gort the Robot
Pascal rozpoczął akcję języka akademickiego, ale pod koniec lat 90-tych to był na krawędzi bycia na język napisać Okna aplikacje w przez Delphi, który jest aż Borland przeskoczył rekina z cen i pozostawił otwarte drzwi dla Java i C # przyjdź i ukraść nagrodę. Nadal jednak zobaczysz tonę Delphi w starszym świecie aplikacji Windows.
Shayne
1

Dlaczego x: int = 42nie int x = 42? Czy ten drugi nie jest bardziej czytelny niż ten pierwszy?

W tak prostym przykładzie nie ma dużej różnicy, ale zróbmy to trochę bardziej złożonym: int* a, b;

To jest rzeczywista, poprawna deklaracja w C, ale nie robi tego, co intuicyjnie wygląda, jak powinna. Wygląda na to, że deklarujemy dwie zmienne typu int*, ale tak naprawdę deklarujemy jedną int*i jedną int.

Jeśli twój język umieszcza typ po nazwie (nazwach) zmiennej w swoich deklaracjach, nie można napotkać tego problemu.

Mason Wheeler
źródło
4
Nie jestem pewien, dlaczego przeniesienie deklaracji typu później miałoby na to wpływ. Czy x, y *int;dwa, *intjeden *inti jeden int? Wykonanie int*lub *intjeden token zamiast dwóch rozwiązałoby go, lub użycie dodatkowego elementu składniowego takiego jak :, który mógłby działać w dowolnym kierunku.
8bittree 19.04.2016
@ 8bittree Każdy język, który znam, który używa deklaracji imienia i nazwiska, używa dodatkowego tokena, takiego jak :lub as, między nazwą a typem.
Mason Wheeler,
2
A w Go x, y *intoznacza „zadeklaruj dwie zmienne, xiy, za pomocą typu wskaźnik do int”.
Vatine
10
C # używa składni prefiksu, ale traktuje int[] x, yjak dwie tablice. To po prostu głupia składnia C, która traktuje te modyfikatory jako jednoargumentowe operatory zmiennej (i mieszankę przedrostka i postfiksu, nie mniej), które powodują problemy.
CodesInChaos 23.04.16