Dlaczego średniki i przecinki są zamieniane na pętle?


49

W wielu językach (szeroka lista, od C do JavaScript):

  • przecinki ,oddzielne argumenty (np. func(a, b, c)), natomiast
  • średniki ;oddzielne instrukcje sekwencyjne (np instruction1; instruction2; instruction3.).

Dlaczego więc odwzorowanie odwrócono w tych samych językach dla pętli :

for ( init1, init2; condition; inc1, inc2 )
{
    instruction1;
    instruction2;
}

zamiast (co wydaje mi się bardziej naturalne)

for ( init1; init2, condition, inc1; inc2 )
{
    instruction1;
    instruction2;
}

?

Jasne, forjest (zazwyczaj) nie jest funkcją, ale argumenty (czyli init, condition, increment) zachowują się bardziej jak argumenty funkcji niż sekwencji instrukcji.

Czy jest to spowodowane względami historycznymi / konwencją, czy też istnieje uzasadnienie dla wymiany ,i ;pętli?


1
(Mój pierwszy post tutaj. Nie jestem pewien, czy to pytanie należy bardziej do Programistów, czy SO, więc możesz migrować, jeśli zajdzie taka potrzeba.)
Piotr Migdal

8
To zdecydowanie post dla programistów. Witamy! :-)
Martijn Pieters

1
„Dlaczego nie” udzieli odpowiedzi - mając taką samą odpowiedź - „Ponieważ ktoś musiał dokonać wyboru, i to jest wybór, którego dokonał” Taki sam jak „Dlaczego wybrał„ {”i„} ”oraz 1000 innych wyborów zrobili - „Ponieważ”.
mattnz

2
@mattnz Pytanie jest o konsystencji (nie „Dlaczego używamy ;nie |?” (lub Dlaczego używamy „inny” nie „inaczej”? )), która nie jest sprawa dla jednego języka, ale duża ich liczba. Odpowiedź, że np. „Został napisany w C jako skrót dla pętli while (i wiele instrukcji dla inc było rozważanych dopiero później), a ludzie nie chcieli jej zmieniać, aby uniknąć irytacji programistów”, byłby w porządku.
Piotr Migdal

Pamiętam czytanie - być może w K&R - że operator przecinka został pierwotnie dodany do języka , aby umożliwić zainicjowanie więcej niż jednej zmiennej w wyrażeniu init instrukcji for.
zwolnić

Odpowiedzi:


18

Dlaczego w tych samych językach takie odwzorowanie jest odwrócone dla pętli.

Technicznie odwzorowanie nie jest „odwrócone”.

  • Rzeczy oddzielone przecinkami nie są parametrami. W (przynajmniej) C ++ i Javie mogą to być deklaracje, więc nie są nawet wyrażeniami.
  • Elementy oddzielone średnikami również nie są (pojedynczymi) instrukcjami.

W rzeczywistości mamy tutaj inny kontekst składniowy, w którym te same symbole są używane w różny sposób. Nie porównujemy z podobnymi, więc nie ma mapowania i nie ma mocnego argumentu za spójnym mapowaniem opartym na spójności semantycznej.

Dlaczego więc nie zrobić tego na odwrót?

Myślę, że przyczyny wynikają z „naturalnego” znaczenia ,i ;. W angielskim języku pisanym średnik to „silniejsza” przerwa niż przecinek, a glif średnika jest bardziej widoczny niż przecinek. Te dwie rzeczy razem sprawiają, że obecny układ wydaje mi się (dla mnie!) Bardziej naturalny.

Ale jedynym sposobem, aby wiedzieć na pewno, dlaczego dokonano wyboru składni, byłoby, gdyby projektanci C mogli powiedzieć nam, co myślą w 1970 roku. Wątpię, aby mieli wyraźną pamięć o decyzjach technicznych podjętych w tak odległych czasach.


Czy wynika to z przyczyn historycznych / konwencji

Nie znam żadnego języka przed C, który używałby składni podobnej do C dla pętli „for”:

  • Donal Fellows zauważa, że ​​BCPL i B nie miały równoważnej konstrukcji.

  • Odpowiedniki FORTRAN, COBOL i Algol-60 (i Pascal) były mniej ekspresyjne i miały składnie, które nie przypominały składni „for”.

Ale języki takie jak C, C ++ i Java, które pojawiły się po C, wyraźnie pożyczają swoją składnię „for” od C.


Tak więc filozofia ( ,vs ;) to (słabsze vs silniejsze zerwanie), a nie (rozdzielacz krotek vs sekwencja), prawda? Nadal dla mnie nie jest oczywiste, czy argumenty lub instrukcje wymagają silniejszych przerw (jak w wielu przypadkach dla sekwencji instrukcji, przerwy są niejawne (patrz np. JavaScript (np. i++[line break]j++))), Ale przynajmniej teraz rozumiem, dlaczego obecna konwencja nie jest „oczywiście odwrócony”.
Piotr Migdal

@PiotrMigdal przecinek jako separator uniemożliwiłby użycie argumentu przecinka i mógłby sugerować, że komponenty pętli for są raczej instrukcjami niż wyrażeniami. Ma to znaczące implikacje.

Ostatni komentarz zainteresował mnie, co zrobił BCPL, ale najwyraźniej tak było FOR i = e1 TO e2 BY e3 DO c(wyrażenia e1..e3, polecenie c), które bardziej przypominają składnię BASIC. Źródło
CVn

1
@PiotrMigdal - „Filozofią” jest to, co K&R, a reszta myślała w 1970 roku. Nie sądzę, aby doszło do głębi myślenia, jakie sobie wyobrażasz. (Próbowali zaimplementować język „wyższego poziomu”, aby uniknąć konieczności pisania masowego oprogramowania przełączników telefonicznych w asemblerze).
Stephen C

Właśnie sprawdziłem; forskładni wprowadzono C (nie było B lub BCPL).
Donal Fellows

60

Piszemy pętle takie jak:

 for(x = 0; x < 10; x++)

Język mógł zostać zdefiniowany tak, aby pętle wyglądały następująco:

 for(x = 0, x < 10, x++)

Pomyśl jednak o tej samej pętli zaimplementowanej za pomocą pętli while:

 x = 0;
 while(x < 10)
 {
     x++;
 }

Zauważ, że instrukcje x=0i x++są zakończone średnikami. Nie są to wyrażenia takie jak w wywołaniu funkcji. Średniki służą do oddzielania instrukcji, a ponieważ dwa z trzech elementów w pętli for są instrukcjami, to właśnie tam są używane. Pętla for to tylko skrót do takiej pętli while.

Ponadto argumenty tak naprawdę nie działają jak argumenty funkcji. Drugi i trzeci są wielokrotnie oceniane. To prawda, że ​​nie są sekwencją, ale nie są też argumentami funkcyjnymi.

Również fakt, że możesz użyć przecinków, aby mieć wiele instrukcji w pętli for, jest w rzeczywistości czymś, co możesz zrobić poza pętlą for.

x = 0, y= 3;

jest całkowicie poprawnym stwierdzeniem, nawet poza pętlą for. Nie znam jednak żadnego praktycznego zastosowania poza pętlą for. Chodzi o to, że przecinki zawsze dzielą instrukcje; nie jest to specjalna funkcja pętli for.


Jasne, rozumiem, że pętla „while” jest „bardziej fundamentalna”. Ale taka „notacja skrótowa” nie ma większego sensu (przynajmniej dla mnie), ponieważ można zacząć od x = 0; y = 0;i (w nawiasach klamrowych) x++; y++;...
Piotr Migdal

2
@PiotrMigdal, oh mógłbyś. Chodzi mi o to, że elementy wewnątrz pętli for są instrukcjami (które są oddzielone średnikami), a nie wyrażeniami (które są oddzielone przecinkami)
Winston Ewert

1
Rozumiem różnicę, dla mnie ;naturalna jest sekwencja instrukcji , niekoniecznie oddzielająca dowolne instrukcje (czy to tylko różnice w gustach?). A w obecnej konwencji czasami zdarza się, że i tak oddzielamy sekwencje zdań przecinkami ...
Piotr Migdal

3
@PiotrMigdal, członkowie struktur / związków są oddzieleni średnikami, ale tak naprawdę nie są one sekwencyjne. Tak więc z pewnością nie jest ograniczony do sekwencji instrukcji w jego użyciu. Pod koniec dnia składnia raczej sprowadza się do smaku.
Winston Ewert

Nie znam jednak żadnego praktycznego zastosowania poza pętlą for --- A może (foo)?bar++, qux++:bletch--- Gdzie chcesz, aby ?:wyrażenie robiło dwie rzeczy zamiast jednej. Wartość zwracana, jeśli foojest prawdą to qux, ale zarówno bari quxdostać zwiększany.

15

W C i C ++ jest to przecinek, a nie przecinek.

Gramatyka forpętli jest podobna

for ([pre-expression]; [terminate-condition]; [increment-expression]) body-expression

W przypadku twojego pytania:

pre-expression -> init1, init2
terminate-condition -> condition
increment-expression -> inc1, inc2

Zauważ, że operator przecinka pozwala wykonać wiele akcji w jednej instrukcji (tak jak widzi to kompilator). Gdyby twoja sugestia została zaimplementowana, gramatyka byłaby niejasna co do tego, kiedy programista zamierzał napisać instrukcję operatora przecinek lub separator.

Krótko mówiąc, ;oznacza koniec instrukcji. forPętla jest słowem kluczowym następuje lista opcjonalnych sprawozdania otoczone (). Instrukcja operator przecinek pozwala na użycie ,w jednej instrukcji.


3
Pętla for to zestaw wyrażeń oddzielonych średnikami. Instrukcje mogą być czymś więcej niż wyrażeniami - nie można wsunąć instrukcji case ani instrukcji if do części pętli for. Istotną implikacją jest stwierdzenie, że elementy pętli for są instrukcjami, gdy spojrzy się na formę bnf pętli for

@MichaelT: Ale w C ++ składnia forpętli wyraźnie zezwala na instrukcję (deklarację) jako jej pierwszą część. (C ++ dopuszczał deklaracje w połowie funkcji, w przeciwieństwie do swojego poprzednika C89). Nie można generalizować takich instrukcji w różnych językach, nawet dla 2 języków tak bliskich jak C i C ++.
MSalters

@MichaelT Tęskniłeś za częścią „is like like”?
James

@James można uniknąć „coś takiego” korzystając z rzeczywistą BNF for ( {<expression>}? ; {<expression>}? ; {<expression>}? ) <statement>dla C i for ( for-init-statement; conditionopt ; expressionopt ) statementdla C ++ --- The „;” oznacza nie tylko terminator instrukcji. Po pętli for nie następują instrukcje zawarte w ().

8

Nie ma odwrócenia pojęciowego.

Średniki w C reprezentują więcej głównych podziałów niż przecinki. Oddzielają oświadczenia i deklaracje.

Główne podziały w pętli for są takie, że istnieją trzy wyrażenia (lub deklaracja i dwa wyrażenia) oraz ciało.

Przecinki, które widzisz w C dla pętli for, nie są częścią składni pętli for. To tylko przejawy operatora przecinka.

Przecinki są głównymi separatorami między argumentami w wywołaniach funkcji i między parametrami w deklaracjach funkcji, ale średniki nie są używane. Pętla for ma specjalną składnię; nie ma to nic wspólnego z funkcjami ani wywołaniami funkcji.


2

Być może jest to coś specyficznego dla C / C ++, ale zamieszczam tę odpowiedź, ponieważ na składnię opisywanych przez ciebie języków zależy głównie od składni C.

Poza tym pytania, na które wcześniej udzielono odpowiedzi, są prawdą, z technicznego punktu widzenia, dzieje się tak również dlatego, że w C (i C ++) przecinek jest tak naprawdę operatorem , który można nawet przeciążać . Użycie operatora średnika ( operator;()) prawdopodobnie utrudniłoby pisanie kompilatorów, ponieważ średnik jest aksjomatycznym terminatorem wyrażeń.

Interesujące jest to, że przecinek jest powszechnie używany jako separator w całym języku. Wygląda na to, że operator przecinka jest wyjątkiem, który jest używany głównie do uzyskiwania forpętli z działającymi wieloma warunkami, więc o co chodzi?

W rzeczywistości operator,jest zbudowany tak, aby robić to samo, co w definicjach, listach argumentów itd.: Został zbudowany w celu oddzielenia wyrażeń - coś, czego ,nie może zrobić konstrukcja składniowa . Może jedynie rozdzielić to, co zostało zdefiniowane w standardzie.

Jednak średnik nie rozdziela się - kończy . I to prowadzi nas z powrotem do pierwotnego pytania:

for (int a = 0, float b = 0.0f; a < 100 && b < 100.0f; a++, b += 1.0f)
    printf("%d: %f", a, b);

Przecinek rozdziela wyrażenia w trzech częściach pętli, podczas gdy średnik kończy część (inicjalizację, warunek lub refleksję) definicji pętli.

Nowsze języki programowania (jak C #) mogą nie pozwalać na przeciążanie operatora przecinków, ale najprawdopodobniej zachowały składnię, ponieważ zmiana wydaje się w jakiś sposób nienaturalna.


Z tym argumentem jest problem. W foroświadczeniu ;symbol jest po prostu używany jako separator. Oddziela 3 składniowe części instrukcji. Nie ma trzeciego średnika do „zakończenia” listy wyrażeń progresywnych. Jest zakończony innym tokenem - ).
Stephen C

0

Dla mnie są one używane w mniejszym stopniu niż ich sens językowy. Przecinki są używane z listami i średnikami z bardziej oddzielnymi częściami.

W func(a, b, c)mamy listę argumentów.

instruction1; instruction2; instruction3 jest może listą, ale listą oddzielnych i niezależnych instrukcji.

Podczas gdy for ( init1, init2; condition; inc1, inc2 )mamy trzy oddzielne części - listę inicjalizacji, warunek i listę wyrażeń przyrostowych.


0

Najłatwiej to zobaczyć:

for(x = 0; x < 10; x++)

jest:

for(
x = 0;
x < 10;
x++
)

Innymi słowy, te x = 0 coś jest w rzeczywistości instrukcją / instrukcją, a nie parametrem. Wstawiasz tam oświadczenie. Dlatego są one oddzielone średnikiem.

W rzeczywistości nie ma możliwości oddzielenia ich przecinkami. Kiedy ostatnio wstawiałeś jako parametr x <10? Robisz to, jeśli chcesz raz komputer x <10 i wstawić wynik tej operacji jako parametr. Tak więc w świecie przecinków wstawiłbyś x <10, jeśli chcesz przekazać wartość x <0 do funkcji.

Tutaj określasz, że program powinien sprawdzać x <10 za każdym razem, gdy pętla jest przekazywana. To jest instrukcja.

x ++ jest zdecydowanie kolejną instrukcją.

To są wszystkie instrukcje. Więc są one rozdzielone średnikiem.


To nie jest oświadczenie. Jest to wyrażenie oddzielone średnikiem. Oświadczenie jest zupełnie inne.

x <10 może być wyrażeniem (które zwykle byłoby oddzielone średnikiem. x = 0 jest zdecydowanie instrukcją / instrukcjami.
user4951

Spójrz na bnf dla C - jeśli pętla for była instrukcją, można użyć innych instrukcji, takich jak inna for switchlub return wewnątrz definicji pętli for (tj. for(int i = 0; if(i > 1024) { return; } ; switch (i % 3) { case 0; case 1: i++; case 2: i++; } ) { ... }) --- nie możesz. To nie jest oświadczenie. Zamiast tego jest zdefiniowany jakofor ( {<expression>}? ; {<expression>}? ; {<expression>}? ) <statement>

Dziwne. int i = 0 jest wyrażeniem w porządku, ale robimy to głównie, aby zadeklarować int, a mianowicie i przypisać mu 0 (zwraca również 0, ale jako efekt uboczny. Nie możesz zrobić dla ({int i = 0; ? j = i}; j <0; cout << "Hello world"), można też tak myślę, że można.
user4951

1
@JimThio: Prawdopodobnie nie zdajesz sobie z tego sprawy, ale „stwierdzenie” i „wyrażenie” mają bardzo dokładne znaczenie w standardach językowych. int i = 0zdecydowanie NIE jest wyrażeniem. Zestaw reguł opisujących wyrażenie jest dość złożony, biorąc pod uwagę to, co może stanowić wyrażenie, ale konstrukcja TYPE NAME = EXPRESSIONnie pasuje do żadnej z tych reguł.
MSalters
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.