Dlaczego identyfikatory nie powinny zaczynać się od liczby?


32

Wygląda na to, że większość języków programowania nie pozwala na zadeklarowanie identyfikatora rozpoczynającego się cyfrą. Byłem ciekawy, dlaczego. Przeszukałem już sieć, ale nie znalazłem satysfakcjonującego wyjaśnienia.


4
Czy masz jeden przykład nazwy zmiennej, w której miałaby ona korzystny wpływ na przejrzystość i czytelność?
Zabezpiecz

5
@Secure: 3dspline, 4seasonPizza, 2pdfConverter, 8bitInt, ...
użytkownik nieznany

6
Dalej pozwala. Z wbudowanych: 2DUP, 2DROP, 2SWAP, 2> R, 2R @, 2R>, 0 = itd.
Peter Mortensen

podobnie jak TCL, ale nie sądzę, aby żadne ze standardowych poleceń TCL zaczynało się od liczby
jk.

Odpowiedzi:


51

W C / C ++ liczba, po której następuje litera, jest uważana za stałą numeryczną, a następujący po niej łańcuch określa jej typ. Na przykład (są to VC ++, nie jestem pewien, jak są one standardowe):

  • 0 - liczba całkowita ze znakiem
  • 0l - podpisana długa liczba całkowita
  • 0u - liczba całkowita bez znaku
  • 0i64 - 64-bitowa liczba całkowita ze znakiem

A) a) łatwiej jest leksykonowi, jak powiedział Daniel, ale także b) robi wyraźne rozróżnienie, ponieważ 0y może być zmienną, ale 0u nigdy nie będzie. Plus inne kwalifikatory, takie jak „i64” zostały dodane znacznie później niż „l” lub „u” i chcą pozostawić opcję dodawania większej liczby, jeśli to konieczne.


7
również liczby szesnastkowe są zapisywane w postaci 0xd +, gdzie d + to jeszcze 1 cyfra szesnastkowa 0-f - więc 0xbeef jest doskonale poprawną „liczbą”.
tcrosley,

20
zdajecie sobie sprawę, że nie wybrałem specyfikacji językowej, ale podałem tylko kilka przykładów ilustrujących tę kwestię, prawda?
DXM

6
Re: „chcą pozostawić opcję dodawania więcej w razie potrzeby”: A C ++ 11 pozwala nawet dodawać własne; patrz http://en.wikipedia.org/wiki/C++11#User-defined_literals .
ruakh

2
Nie sądzę, żeby to było właściwe wytłumaczenie. Reguła „identyfikator nie może zaczynać się cyfrą” była prawdziwa w przypadku Algolu, Pascala i innych języków, które nie pozwalały na przyrostki alfabetyczne na stałe liczbowe.
Larry Gritz

1
@ LarryGritz: „Konsekwentne rozdzielanie słów spacjami stało się powszechnym zwyczajem około X wieku naszej ery i trwało do około 1957 r., Kiedy FORTRAN porzucił tę praktykę”. —Sun Podręcznik referencyjny FORTRAN (z wiki). Fortran miał swoje specjalne powody, ponieważ zdecydowali, że spacje są w ogóle opcjonalne. NOWOCZESNE języki lubią ich białe znaki. Jesteś z Algolem na własną rękę, ale ja też nie jestem taki nowoczesny. Z drugiej strony C / C ++ / C # / F # mają sufiksy.
DXM

49

Wygoda osób wdrażających leksykon. (Nie, poważnie, o to chodzi. Różne języki mają inne powody, ale ostatecznie sprowadza się do tego.)


2
Łatwo byłoby rozróżnić integralne literały i identyfikatory, zaczynając od cyfr za pomocą PEG lub innych nowoczesnych technik analizy. Nawet kompilatory używające prymitywnych leksyków mogą umieścić je w tej samej kategorii znaczników i później rozróżnić. Byłoby po prostu bardzo niezręcznie, gdyby np. 0fluBył dosłowny i 0glubył lokalnym identyfikatorem.
Daniel Lubarov

2
To jest absolutnie możliwe , by ludzie mogli je odróżnić. Decyzja jest podejmowana na podstawie wygody (lub, jeśli jesteś mniej charytatywny, lenistwa), a nie wymagań technicznych.
Daniel Pittman

2
@DanielPittman: Potrzebowałbyś analizy semantycznej, aby dokonać jakiejkolwiek wiarygodnej dwuznaczności, więc nie można tego zrobić w lekturze. Odsunięcie decyzji od leksera powoduje, że parser jest bardziej złożony i z jakiej korzyści? Oprócz bardzo złej sytuacji koszt / korzyść, po prostu nie ma dobrego sposobu, aby poradzić sobie z takim przypadkiem. int 0u = 5; unsigned int x = 0u;Jednak zdecydujesz się zdefiniować interpretację tego kodu (prawdopodobnie x == 0 lub x == 5), ludzie będą zdezorientowani z powodu niejasności. Nawet gdyby wdrożenie kompilatora w ten sposób było banalne, dobry projektant prawdopodobnie by tego nie zrobił.
Joren

10
Główną wygodą jest parser w mojej głowie, a nie twórca języka.
CodesInChaos

2
Wiele osób wciąż zaskakuje fakt, że analiza leksykalna jest zwykle dużym czynnikiem najwolniejszym etapem kompilatora / tłumacza.
hippietrail

20

Rozważ następujące 2 przypadki:

Przypadek 1

Załóżmy, że identyfikator może zaczynać się od liczby.

Tak więc zdanie takie jak poniżej byłoby prawidłowe (ponieważ identyfikator może mieć 1 lub więcej znaków):

int 3;

Kiedy spróbuję użyć powyższej zmiennej w programie, spowoduje to niejednoznaczność kompilatora:

int 3, a;
3 = 5;
a = 3;

W oświadczeniu a=3jaka jest rola 3 (czy jest to zmienna o wartości 5, czy też jest liczbą 3)?

Przypadek 2

W przeciwieństwie do powyższego przykładu, załóżmy, że język faktycznie pozwalałby na identyfikatory zaczynające się od liczby, a jednocześnie nie pozwalał na używanie liczb jako identyfikatorów. Może to powodować następujące problemy:

  • Reguły językowe dotyczące zmiennej, która mówi, że zmienna może składać się z 1 lub więcej znaków, będą musiały zostać ponownie zdefiniowane do złożonej reguły, takiej jak: Zmienna może mieć jeden lub więcej znaków i musi być unikalna, jeśli nie zaczyna się na cyfrze nie może mieć długości jednego znaku, zaczynając od cyfry (itp.)

  • Kompilator będzie musiał sprawdzić i zgłosić przypadki błędów, gdy wszystkie nazwy (np. 333) i poprawne sufiksy alfabetu (np. 34L) są używane jako nazwy zmiennych. W luźno wpisanych językach, takich jak Python i JS, w których można używać zmiennych w locie bez ich deklarowania, może nawet być niemożliwe sprawdzenie specjalnych przypadków obejmujących wszystkie cyfry, np. if (33==5)Tutaj 33 może być błędną niezadeklarowaną zmienną zadeklarowaną przez użytkownika. Ale kompilator nie będzie w stanie tego zidentyfikować i zgłosić błędu.

Wprowadzenie tego ograniczenia uniemożliwi programistom używanie numerów jako nazw identyfikatorów.


2
Zgodnie z tą logiką identyfikatory nie mogą zawierać znaków, ponieważ byłyby niejednoznaczne względem słów kluczowych. Czy możesz sobie wyobrazić, jak katastrofalne int char = floatbyłoby?
Pubby

4
@Pubby: Nie rozumiem, w jaki sposób można ekstrapolować to, co powiedziałem, do jakiegoś kompletnego braku sensu, którego nie mogę jeszcze rozgryźć. Co oznacza twój komentarz?
aml90

Mówię, że traktujesz to pytanie zbyt dosłownie i że nie jest to wcale dwuznaczne, stosując pierwszeństwo leksykalne. Na przykład, skąd kompilator wie, że intjest słowem kluczowym, a nie identyfikatorem? Cóż, intma wyższy priorytet tak numical leksemy miałby.
Pubby

@Pubby: Przez dwuznaczność miałem na myśli, że kompilator nie będzie wiedział, w jakim kontekście używam nazwy zmiennej (nawet używając pierwszeństwa leksykalnego). Rozważmy na przykład ten kod: int 3,a; 3=5; a=3; w wyrażeniu a = 3, czy 3 jest interpretowane jako identyfikator czy jako liczba? To powoduje niejednoznaczność. Mam nadzieję, że to jasne.
aml90

2
Uważam również, że ten argument jest słaby. Byłoby trywialne napisanie leksera, który akceptowałby identyfikatory rozpoczynające się cyframi, ale nie do nich w całości złożone.
Larry Gritz

11

W przeważającej części nie ma to nic wspólnego z ułatwieniem twórcom kompilatorów i wydajnością analizowania, ale bardziej z zaprojektowaniem składni, która zachęca do czytelnego i jednoznacznego kodu.

Projektanci języka uznali, że fajnie byłoby pisać literały liczbowe takie jak cyfra 1 jako zwykły 1 .

Byłoby całkiem możliwe zaprojektowanie składni języka, w której literały liczbowe były cytowane w jakiś sposób, na przykład tylda, więc literał liczbowy dla liczby pierwszej był zakodowany jako ~ 1 ~ i wszystko, co nie było słowem kluczowym i nie było zawarte w cudzysłowach, było traktowane jako nazwa zmiennej .

Abyś mógł kodować takie instrukcje, jak:

1 = ~2~
two = 1 * ~2~

Ale również:

2 = ~3~
six = 2 + 2

Niezależnie od wybranej składni niejednoznaczny i trudny do przestrzegania kod jest nieunikniony.

Język C i większość języków „nawiasów klamrowych” wywodzących się z C również uważało za dobry pomysł, aby umożliwić programistom bezpośrednie kodowanie literałów ósemkowych i szesnastkowych oraz, jeśli to ważne, określić typ literału. Więc

010  // Octal 10 = 8;
0x10 // Hexadecimal 10 = 16;
5l   // long integer with decimal value 5
2.0d // double float with value 2

Więc nawet jeśli pozwolisz, aby nazwy zmiennych zaczynały się od liczby, po której następuje kombinacja liczb i litery zawierającej co najmniej jedną literę, chciałbyś programiście rozwiązać problem decydowania, czy dana grupa utworzy nazwę zmiennej, czy też literał liczbowy, więc

2lll = 22 // OK
2ll  = 2  // compiler error

Taka dwuznaczność nie pomogłaby nikomu pisać lub czytać programu.

Jako ściśle powiązany przykład ze świata rzeczywistego można przyjrzeć się językowi PL / 1, którego projektanci uznali, że możliwość użycia słów kluczowych jako nazw zmiennych jest dobrym pomysłem, aby:

IF THEN THEN THEN = ELSE; ELSE ELSE = THEN;
IF IF THEN ELSE = IF; ELSE THEN = ELSE;
DO WHILE (WHILE = DO); END = WHILE + DO; END;

Jest poprawnym kodem, który się kompiluje i wykonuje.


C został zaprojektowany jako przenośny zestaw dla Uniksa. Unix został pierwotnie zaprojektowany dla maszyny 18-bitowej, gdzie ósemka jest dobrze dopasowana do drukowania w ten sam sposób, w jaki hex jest dobrym dopasowaniem do drukowania wartości 8/16/32-bitowych. Dlatego rzeczywiście potrzebowali ósemki.

Również w przypadku kręcenia bitów (OR, XOR, AND, NOT) i wdrażania sterowników urządzeń ważne jest, aby określić dokładny rozmiar literału, a także wartość!
James Anderson

10

Fortran miał ogromny wpływ na sposób projektowania późniejszych języków. Na początku (niektóre z tych problemów zostały już naprawione) Fortran nie miał prawie żadnych reguł ograniczających nazwę, którą można nadać identyfikatorowi. To bardzo utrudniało analizowanie języka zarówno dla kompilatorów, jak i dla programistów. Oto jeden klasyczny przykład:

if if .eq. then then = else else else = endif endif
K  I   K   K    I      I    K    I      I     K

Tutaj zaznaczyłem „kluczowe słowa języka” za pomocą K i identyfikatorów (nazw zmiennych) I. Biorąc pod uwagę, że nie ma różnicy w pisowni, myślę, że prawdopodobnie możesz zrozumieć, jak może to być mylące. Oczywiście jest to skrajny przykład i jest mało prawdopodobne, aby ktokolwiek kiedykolwiek celowo napisał taki kod. Czasami ludzie nie „utylizacja” Język słów kluczowych jak nazwy identyfikatorów chociaż - iw wielu przypadkach prosty literówka mogą wyniknąć w kodzie, który spec język wymienionego powinien być analizowany w ten sposób, mimo że nie miała w ogóle. W innym dobrze znanym przykładzie porównaj to:

do 10 i = 1,10

do tego:

do 10 i = 1.10

Pierwsza to pętla do - iterująca blok kodu 10 razy. W drugim przypadku przecinek został zmieniony na przecinek dziesiętny, więc przypisuje wartość 1.10do zmiennej o nazwie do 10 i.

Oznaczało to również, że napisanie parsera Fortrana było stosunkowo trudne - nie można było być pewnym, że dona początku wiersza było naprawdę kluczowe słowo, dopóki nie osiągnięto końca wiersza, i zweryfikowano, że wszystkie pozostałe elementy dopętla była obecna. Parser generalnie musiał być gotowy do „cofnięcia się”, parsowania wiersza od początku, aby dojść do „poprawnej” (ale często niezamierzonej) odpowiedzi na to, co naprawdę tam było.

Po kilku latach projektanci języków (większość z nich i tak) poszli w kierunku skrajnej skrajności - ograniczając prawie wszystko w języku tak bardzo, jak to możliwe, bez zbytniego narzekania użytkowników .

Na przykład wczesny język BASIC w zasadzie powiedział, że nie można nawet użyć słowa kluczowego jako części identyfikatora - na przykład fora=1zostałby przeanalizowany jako for a = 1(tj. Początek forpętli, a nie przypisanie). To najwyraźniej wywołało wystarczająco dużo skarg, że nie trwało to długo. Reguła dotycząca rozpoczynania identyfikatora cyfrą najwyraźniej nie spowodowała wielu skarg, więc nadal jest używana (przynajmniej w większości języków).


IMHO to najbliżej prawdziwego powodu. Wczesne języki, takie jak Fortran, były pod pewnymi względami zbyt nieuporządkowane, co prowadziło do trudności z pisaniem solidnych kompilatorów i trudności z poprawnym wizualnym analizowaniem kodu źródłowego. „Do10i = ...” jest klasycznym i znanym przykładem. W miarę ewolucji języków niektóre zasady zostały zaostrzone. Algol jest prawdopodobnie dziadkiem standardowej reguły „identyfikatory zaczynają się od liter, a następnie mogą mieć literę lub cyfrę”.
Larry Gritz

FYI, interpreter Microsoft BASIC, który stanowił podstawę najpopularniejszych wersji BASIC mikrokomputera (w tym Applesoft Basic i Commodore Basic), użył chciwego tokenizera do konwersji dowolnej sekwencji znaków, która pasowała do tokenu języka, na wartość bajtową z wysokim zestawem bitów. Dokonano tego bez żadnej analizy składniowej. Następnie podczas uruchamiania programu interpreter zakłada, że ​​znalezione litery stanowią część nazwy zmiennej.
supercat

1

Prawdopodobnie konwencja ta ewoluowała od bardzo wczesnych decyzji dotyczących projektowania języka historycznego, ponieważ na wczesnych komputerach cały kompilator, w tym analiza leksykalna, musiał działać z kilkoma kWordami, mniej pamięci niż nawet pamięć podręczna danych procesora pierwszego poziomu na obecnych urządzeniach mobilnych, więc dozwolone nazwy zmiennych były bardzo ograniczone i musiały być łatwe do odróżnienia od stałych numerycznych w bardzo niewielu kodach operacyjnych.

W ten sposób konwencja stała się tym, do czego przyzwyczajone są pokolenia programistów.


1

Nie jest to logicznie wymagana reguła dla języka programowania, ale tylko konwencja stosowana przez wielu projektantów języków.

Potrafię zaprojektować radykalnie inny język, który pozwala na wszystkie znaki dla identyfikatorów. Dla wszystkich wierszy kodu pierwsze 20 znaków opisuje typ instrukcji, a następnie 20 znaków określa pierwszy symbol instrukcji, a kolejne 20 znaków jest operandem instrukcji. Ten język zostanie wykonany na procesorze stosu.

01234567890123456789 01234567890123456789 01234567890123456789

decl symbol          12345                
assign value         12345                12345
decl symbol          99999                
assign value         99999                12345
push                 12345
push                 99999
add
print top

Ten kod można przetłumaczyć na C jak poniżej:

int i12345 = 12345;
int i99999 = 12345;
printf("%d", i12345+i9999);

To wszystko. Jest bez znaczenia, a zasada braku numeru w identyfikatorach jest również bezcelowa z logicznego punktu widzenia.


0

Myślę, że oprócz „wygody dla leksera” warto również rozważyć „wygodę dla czytelnika”.

Czytając kod, musisz szybko i wielokrotnie identyfikować, które słowa są identyfikatorami, a które liczbami. Szukanie cyfry na początku jest łatwiejsze dzięki naszemu wizualnemu dopasowaniu wzorów; byłoby ciężkim obowiązkiem, gdybyśmy musieli dokładnie sprawdzić wszystkie postacie, aby się upewnić.


0

Odpowiedź na to pytanie leży w automatach, a dokładniej automatach skończonych, które definiują wyrażenie regularne. Zasada jest taka ... kompilatory potrzebują dokładnych algorytmów lub reguł, aby decydować przy każdym analizowanym znaku. Jeśli identyfikatory miałyby zaczynać się od liczby, to kompilator będzie w poprawce ... o charakterze nadchodzącego tokena ... czy będzie to liczba lub identyfikator ... i jako kompilatory nie mogą wrócić do wcześniejszych pozycji ... .so .. aby wyjaśnić kompilatorowi, że nadchodzący token jest dokładnie identyfikatorem lub liczbą ... to ograniczenie istnieje ... ponieważ ten ... kompilator wie, skanując pierwszy znak, że nadchodzący token jest identyfikatorem lub liczbą.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.