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 = 42
nie 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?
programming-languages
language-design
syntax
Andre Polykanine
źródło
źródło
Odpowiedzi:
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
var
sł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 referencjiconst
,volatile
i tak dalej. Kiedy wrzucisz przecinki dla wielu deklaracji, zaczynasz odczuwać naprawdę dziwne dwuznaczności, takie jakint* a, b;
nie tworzenieb
wskaźnika.Nawet C ++ obsługuje teraz deklaracje „wpisz po prawej” w formie
auto x = int{ 4 };
i ma pewne zalety .źródło
var
zapewnia większość uproszczenia analizy.var
słowa kluczowego nie jest sprzeczne z celem notacji „pierwsze imię”? Nie widzę tę zaletę, piszącvar userInput: String = getUserInput()
naString userInput = getUserInput()
. Korzyść byłaby istotna tylko wtedy, gdyuserInput = getUserInput()
jest dozwolona, ale oznaczałoby to niejawną deklarację zmiennej o zasięgu, co wydaje mi się dość odrażające.var userInput = getUserInput()
lubauto userInput = getUserInput()
, kompilator znagetUserInput()
zwroty,String
więc nie musisz go określać słowem kluczowym dedukcji typu.userInput = getUserInput()
jest oczywiście używany przed deklaracją.Twoje założenie jest wadliwe na dwóch frontach:
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.
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 .
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:
W takim razie pominięcie tego typu i wyciągnięcie z niego wniosku jest takie proste:
Natomiast jeśli typ występuje przed takim identyfikatorem:
Następnie parser zaczyna mieć trudności z odróżnieniem wyrażenia od deklaracji:
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:
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 ):
FAQ Kotlin :
Programowanie w Scali :
Uwaga: projektanci Ceylonu również udokumentowali, dlaczego używają składni typu prefiksu :
Osobiście uważam, że ich „argument” jest o wiele mniej przekonujący niż inni.
źródło
var
,auto
,let
, lub tym podobne, a nie za pomocą których strony deklaracja zmiennej typu jest włączony.var Int x = 5
lub nawetInt var x = 5
można je skrócićvar x = 5
."Your premise is flawed on two fronts"
Wszystkie są nowsze niż C # lub D.func
w Go.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.
źródło
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.
źródło
x, y *int;
dwa,*int
jeden*int
i jedenint
? Wykonanieint*
lub*int
jeden 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.:
lubas
, między nazwą a typem.x, y int
bez separatora.x, y *int
oznacza „zadeklaruj dwie zmienne, xiy, za pomocą typu wskaźnik do int”.int[] x, y
jak 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.