Dlaczego języki programowania, zwłaszcza C, używają nawiasów klamrowych, a nie kwadratowych?


96

Definicję „języka w stylu C” można praktycznie uprościć do „używa nawiasów klamrowych ( {})”. Dlaczego używamy tej konkretnej postaci (i dlaczego nie coś bardziej rozsądnego, na przykład [], co nie wymaga klawisza Shift przynajmniej na klawiaturach w USA)?

Czy jest jakaś rzeczywista korzyść dla wydajności programisty pochodzącej z tych nawiasów, czy też nowi projektanci języków powinni szukać alternatyw (np. Facetów stojących za Pythonem)?

Wikipedia mówi nam, że C używa wspomnianych aparatów ortodontycznych, ale nie dlaczego. Oświadczenie w artykule w Wikipedii na temat listy języków programowania opartych na języku C sugeruje, że ten element składniowy jest nieco wyjątkowy:

Mówiąc ogólnie, języki rodziny C to te, które używają składni bloków podobnych do C (w tym nawiasów klamrowych do rozpoczynania i kończenia bloku) ...


35
Jedyną osobą, która może na to odpowiedzieć, jest Dennis Ritchie i nie żyje. Rozsądnym przypuszczeniem jest to, że [] zostały już wykorzystane do tablic.
Dirk Holsopple

2
@DirkHolsopple Więc nie pozostawił żadnego rozumowania? Drat. Ponadto: dwa opinie negatywne na temat czegoś, co naprawdę mnie interesuje? Dzięki chłopaki ....
SomeKittens

1
Kontynuuj dyskusję na temat tego pytania w tym pytaniu Meta .
Thomas Owens

2
Odblokowałem ten post. Zachowaj wszelkie uwagi dotyczące pytania i dyskusje na temat stosowności pytania dotyczącego Meta .
Thomas Owens

5
Prawdopodobnie ma to również coś wspólnego z faktem, że nawiasy klamrowe są używane w notacji zestawów w matematyce, co sprawia, że ​​są one nieco niewygodne w przypadku dostępu do elementów tablicy, a nie takie rzeczy, jak deklarowanie „ustawionych” elementów takich jak struktury, tablice itp. Nawet współczesne języki, takie jak Python, używają nawiasów klamrowych do deklarowania zbiorów i słowników. Pytanie brzmi zatem, dlaczego C użył również nawiasów klamrowych do zadeklarowania zakresu? Prawdopodobnie dlatego, że projektanci po prostu nie lubili znanych alternatyw, takich jak BEGIN / END, a przeciążenie notacji dostępu do tablicy ([]) zostało uznane za mniej estetyczne niż notacja ustawiona.
Charles Salvia,

Odpowiedzi:


102

Dwa główne czynniki wpływające na C to rodzina języków Algol (Algol 60 i Algol 68) i BCPL (od której C bierze swoją nazwę).

BCPL był pierwszym językiem programowania nawiasów klamrowych, a nawiasy klamrowe przetrwały zmiany składniowe i stały się powszechnym środkiem oznaczania instrukcji kodu źródłowego programu. W praktyce, na ograniczonych klawiaturach dnia, programy źródłowe często używały sekwencji $ (i $) zamiast symboli {i}. Komentarze jednowierszowe „//” BCPL, które nie zostały uwzględnione w C, pojawiły się ponownie w C ++, a później w C99.

From http://www.princeton.edu/~achaney/tmve/wiki100k/docs/BCPL.html

BCPL wprowadził i wdrożył kilka innowacji, które stały się dość powszechnymi elementami w projektowaniu późniejszych języków. Tak więc był to pierwszy język programowania w nawiasach klamrowych (taki, w którym jako separatory bloków używano {}), a także jako pierwszy używano // do oznaczania wbudowanych komentarzy.

From http://progopedia.com/language/bcpl/

W ramach BCPL często widać nawiasy klamrowe, ale nie zawsze. Było to wówczas ograniczenie klawiatury. Znaki $(i $)były leksykograficznie równoważne do {i }. Digraphy i trigrafy były utrzymywane w C (choć inny zestaw do wymiany nawiasów klamrowych - ??<i ??>).

Zastosowanie nawiasów klamrowych zostało dodatkowo udoskonalone w B (które poprzedziło C).

Od odniesienia użytkowników do B autorstwa Kena Thompsona:

/* The following function will print a non-negative number, n, to
  the base b, where 2<=b<=10,  This routine uses the fact that
  in the ASCII character set, the digits 0 to 9 have sequential
  code values.  */

printn(n,b) {
        extern putchar;
        auto a;

        if(a=n/b) /* assignment, not test for equality */
                printn(a, b); /* recursive */
        putchar(n%b + '0');
}

Istnieją przesłanki, że nawiasy klamrowe były używane jako krótka ręka w Algolu begini endwewnątrz niego.

Pamiętam, że umieściłeś je również w 256-znakowym kodzie karty, który opublikowałeś w CACM, ponieważ uznałem za interesujące, że zaproponowałeś, aby można je było stosować zamiast słów kluczowych „początek” i „koniec” Algolu, czyli dokładnie jak zostały później użyte w języku C.

Od http://www.bobbemer.com/BRACES.HTM


Zastosowanie nawiasów kwadratowych (jako sugerowanego zamiennika w pytaniu) sięga jeszcze dalej. Jak wspomniano, rodzina Algola miała wpływ na C. W Algolu 60 i 68 (C napisano w 1972 r. I BCPL w 1966 r.) Nawias kwadratowy zastosowano do oznaczenia indeksu w tablicy lub macierzy.

BEGIN
  FILE F(KIND=REMOTE);
  EBCDIC ARRAY E[0:11];
  REPLACE E BY "HELLO WORLD!";
  WRITE(F, *, E);
END.

Ponieważ programiści znali już nawiasy kwadratowe dla tablic w Algolu i BCPL oraz nawiasy klamrowe dla bloków w BCPL, nie było potrzeby ani chęci zmiany tego przy tworzeniu innego języka.


Zaktualizowane pytanie zawiera dodatek produktywności dla użycia nawiasów klamrowych i wspomina o pythonie. Istnieją inne zasoby, które zajmują się tym badaniem, chociaż odpowiedź sprowadza się do „Jest to anegdota, a to, do czego jesteś przyzwyczajony, to to, z czego jesteś najbardziej produktywny”. Ze względu na bardzo różne umiejętności programowania i znajomość różnych języków, stają się one trudne do wytłumaczenia.

Zobacz także: Przepełnienie stosu Czy istnieją badania statystyczne wskazujące, że Python jest „bardziej produktywny”?

Wiele korzyści będzie zależało od zastosowanego IDE (lub jego braku). W edytorach opartych na vi, umieszczenie kursora nad jednym pasującym otwieraniem / zamykaniem i naciśnięcie %spowoduje przeniesienie kursora do drugiego pasującego znaku. Jest to bardzo wydajne w przypadku języków opartych na C w dawnych czasach - teraz mniej.

Lepszym porównaniem byłoby między {}i begin/ endktóre były opcjami dnia (przestrzeń pozioma była cenna). Wiele języków Wirth opartych było na stylu begini end(Algol (wspomniany powyżej), pascal (wielu zna) i rodzina Modula).

Mam trudności ze znalezieniem tych, które izolują tę konkretną cechę języka - co mogę zrobić, to pokazać, że języki nawiasów klamrowych są znacznie bardziej popularne niż języki początkowe i jest to wspólna konstrukcja. Jak wspomniano w linku Boba Bemera powyżej, nawias klamrowy został użyty, aby ułatwić programowanie jako skrót.

Od Dlaczego Pascal nie jest moim ulubionym językiem programowania

Programiści C i Ratfor uważają, że „początek” i „koniec” są nieporęczne w porównaniu do {i}.

To wszystko, co można powiedzieć - jego znajomość i preferencje.


14
Teraz wszyscy tutaj uczą się BCPL zamiast pracować :)
Denys Séguret

Trygrafy (wprowadzone w standardzie ISO C 1989) dla {i }??<i ??>. Drafrafami (wprowadzonymi poprawką z 1995 r.) Są <%i %>. Trigrafie są rozszerzane we wszystkich kontekstach, w bardzo wczesnej fazie tłumaczenia. Cyfry to tokeny i nie są rozwijane w literałach łańcuchowych, stałych znaków ani komentarzy.
Keith Thompson

W C istniało coś takiego przed 1989 rokiem (musiałbym wykopać książkę z pierwszego wydania, żeby się z tym umówić). Nie wszystkie strony kodowe EBCDIC zawierały nawiasy klamrowe (lub nawiasy kwadratowe) i były do ​​tego odpowiednie w najwcześniejszych kompilatorach C.

@NevilleDNZ BCPL używał nawiasów klamrowych w 1966 roku. Skąd Algol68 czerpał swoje wyobrażenie, byłoby czymś do zbadania - ale BCPL nie otrzymał go od Algo68. Operator trójskładnikowy jest czymś, co mnie interesowało i prześledziłem go z powrotem do CPL (1963) (poprzednik BCPL), który zapożyczył to pojęcie z Lisp (1958).

1968: Algol68 pozwala okrągłych nawiasów (~) jako skrót od rozpocząć ~ końcowych śmiałe bloki symboli. Są to tak zwane krótkie symbole, patrz wp: Algol68 Pogrubione symbole , pozwala to traktować bloki kodu tak jak wyrażenia . A68 posiada także krótkie Shorthands jak C za : potrójnego operatora np x:=(c|s1|s2)zamiast C użytkownika x=c?s1|s2. Podobnie dotyczy to instrukcji if i case . ¢ BTW: A68 pochodzi z miejsca, w którym powłoka ma swój esac i fi ¢
NevilleDNZ

24

Kwadratowe nawiasy klamrowe []są łatwiejsze do wpisania, odkąd terminal IBM 2741, który był „szeroko stosowany w systemie operacyjnym Multics” , który z kolei miał Dennisa Ritchiego, jednego z twórców języka C jako członka zespołu programistów .

http://upload.wikimedia.org/wikipedia/commons/thumb/9/9f/APL-keybd2.svg/600px-APL-keybd2.svg.png

Zwróć uwagę na brak nawiasów klamrowych w układzie IBM 2741!

W C nawiasy kwadratowe są „pobierane”, ponieważ są używane do tablic i wskaźników . Jeśli projektanci języków spodziewali się, że tablice i wskaźniki będą ważniejsze / używane częściej niż bloki kodu (co brzmi jak rozsądne założenie po ich stronie, bardziej w kontekście historycznego stylu kodowania poniżej), oznaczałoby to, że nawiasy klamrowe byłyby „mniej ważne „składnia.

Znaczenie tablic jest dość widoczne w artykule The Development of the C Language autorstwa Ritchie. Istnieje nawet wyraźnie określone założenie „rozpowszechnienia wskaźników w programach C” .

... nowy język zachował spójne i wykonalne (jeśli nietypowe) wyjaśnienie semantyki tablic ... Dwie idee są najbardziej charakterystyczne dla C wśród języków jego klasy: związek między tablicami i wskaźnikami ... Inna charakterystyczna cecha C, jego traktowanie tablic ... ma prawdziwe zalety . Chociaż związek między wskaźnikami a tablicami jest niezwykły, można się tego nauczyć. Co więcej, język wykazuje znaczną moc opisywania ważnych pojęć, na przykład wektorów, których długość zmienia się w czasie wykonywania, z jedynie kilkoma podstawowymi zasadami i konwencjami ...


Aby lepiej zrozumieć kontekst historyczny i styl kodowania w czasie, gdy język C był tworzony, należy wziąć pod uwagę, że „pochodzenie C jest ściśle związane z rozwojem Uniksa”, a w szczególności przenosi system operacyjny na PDP- 11 „doprowadziło do opracowania wczesnej wersji C” ( źródło cytatów ). Według Wikipedii , „w 1972 roku, Unix został przepisany w języku programowania C” .

Kod źródłowy różnych starych wersji Uniksa jest dostępny online, np. Na stronie The Unix Tree . Spośród różnych przedstawionych tam wersji, najbardziej odpowiednia wydaje się być Second Edition Unix z 1972-06:

Druga edycja Uniksa została opracowana dla PDP-11 w Bell Labs przez Kena Thompsona, Dennisa Ritchie i innych. Rozszerzył pierwszą edycję o więcej wywołań systemowych i więcej poleceń. W tej edycji pojawił się także początek języka C, który był używany do pisania niektórych poleceń ...

Możesz przeglądać i studiować kod źródłowy C ze strony drugiej edycji Uniksa (V2), aby uzyskać wyobrażenie o typowym stylu kodowania tamtych czasów.

Wybitny przykład, który popiera pomysł, że w tamtym czasie programiści mogli z łatwością pisać nawiasy kwadratowe, można znaleźć w kodzie źródłowym V2 / c / ncc.c :

/* C command */

main(argc, argv)
char argv[][]; {
    extern callsys, printf, unlink, link, nodup;
    extern getsuf, setsuf, copy;
    extern tsp;
    extern tmp0, tmp1, tmp2, tmp3;
    char tmp0[], tmp1[], tmp2[], tmp3[];
    char glotch[100][], clist[50][], llist[50][], ts[500];
    char tsp[], av[50][], t[];
    auto nc, nl, cflag, i, j, c;

    tmp0 = tmp1 = tmp2 = tmp3 = "//";
    tsp = ts;
    i = nc = nl = cflag = 0;
    while(++i < argc) {
        if(*argv[i] == '-' & argv[i][1]=='c')
            cflag++;
        else {
            t = copy(argv[i]);
            if((c=getsuf(t))=='c') {
                clist[nc++] = t;
                llist[nl++] = setsuf(copy(t));
            } else {
            if (nodup(llist, t))
                llist[nl++] = t;
            }
        }
    }
    if(nc==0)
        goto nocom;
    tmp0 = copy("/tmp/ctm0a");
    while((c=open(tmp0, 0))>=0) {
        close(c);
        tmp0[9]++;
    }
    while((creat(tmp0, 012))<0)
        tmp0[9]++;
    intr(delfil);
    (tmp1 = copy(tmp0))[8] = '1';
    (tmp2 = copy(tmp0))[8] = '2';
    (tmp3 = copy(tmp0))[8] = '3';
    i = 0;
    while(i<nc) {
        if (nc>1)
            printf("%s:\n", clist[i]);
        av[0] = "c0";
        av[1] = clist[i];
        av[2] = tmp1;
        av[3] = tmp2;
        av[4] = 0;
        if (callsys("/usr/lib/c0", av)) {
            cflag++;
            goto loop;
        }
        av[0] = "c1";
        av[1] = tmp1;
        av[2] = tmp2;
        av[3] = tmp3;
        av[4] = 0;
        if(callsys("/usr/lib/c1", av)) {
            cflag++;
            goto loop;
        }
        av[0] = "as";
        av[1] = "-";
        av[2] = tmp3;
        av[3] = 0;
        callsys("/bin/as", av);
        t = setsuf(clist[i]);
        unlink(t);
        if(link("a.out", t) | unlink("a.out")) {
            printf("move failed: %s\n", t);
            cflag++;
        }
loop:;
        i++;
    }
nocom:
    if (cflag==0 & nl!=0) {
        i = 0;
        av[0] = "ld";
        av[1] = "/usr/lib/crt0.o";
        j = 2;
        while(i<nl)
            av[j++] = llist[i++];
        av[j++] = "-lc";
        av[j++] = "-l";
        av[j++] = 0;
        callsys("/bin/ld", av);
    }
delfil:
    dexit();
}
dexit()
{
    extern tmp0, tmp1, tmp2, tmp3;

    unlink(tmp1);
    unlink(tmp2);
    unlink(tmp3);
    unlink(tmp0);
    exit();
}

getsuf(s)
char s[];
{
    extern exit, printf;
    auto c;
    char t, os[];

    c = 0;
    os = s;
    while(t = *s++)
        if (t=='/')
            c = 0;
        else
            c++;
    s =- 3;
    if (c<=8 & c>2 & *s++=='.' & *s=='c')
        return('c');
    return(0);
}

setsuf(s)
char s[];
{
    char os[];

    os = s;
    while(*s++);
    s[-2] = 'o';
    return(os);
}

callsys(f, v)
char f[], v[][]; {

    extern fork, execv, wait, printf;
    auto t, status;

    if ((t=fork())==0) {
        execv(f, v);
        printf("Can't find %s\n", f);
        exit(1);
    } else
        if (t == -1) {
            printf("Try again\n");
            return(1);
        }
    while(t!=wait(&status));
    if ((t=(status&0377)) != 0) {
        if (t!=9)       /* interrupt */
            printf("Fatal error in %s\n", f);
        dexit();
    }
    return((status>>8) & 0377);
}

copy(s)
char s[]; {
    extern tsp;
    char tsp[], otsp[];

    otsp = tsp;
    while(*tsp++ = *s++);
    return(otsp);
}

nodup(l, s)
char l[][], s[]; {

    char t[], os[], c;

    os = s;
    while(t = *l++) {
        s = os;
        while(c = *s++)
            if (c != *t++) goto ll;
        if (*t++ == '\0') return (0);
ll:;
    }
    return(1);
}

tsp;
tmp0;
tmp1;
tmp2;
tmp3;

Warto zauważyć, jak pragmatyczna motywacja wybierania znaków w celu oznaczenia elementów składni języka na podstawie ich użycia w ukierunkowanych praktycznych zastosowaniach przypomina Prawo Zipfa, jak wyjaśniono w tej wspaniałej odpowiedzi ...

zaobserwowany związek między częstotliwością a długością nazywa się prawem Zipfa

... z tą różnicą, że długość powyższego wyrażenia jest zastępowana / uogólniona jako szybkość pisania.


5
Coś na poparcie tego „pozornego” oczekiwania projektantów języka? Nie trzeba dużo programowania w C, aby zauważyć, że nawiasy klamrowe są znacznie bardziej powszechne niż deklaracje tablicowe. Tak naprawdę niewiele się zmieniło od czasów dawnych - spójrz na K&R.

1
Jakoś wątpię w to wyjaśnienie. Nie wiemy, czego się spodziewaliśmy i mogli z łatwością wybrać go na odwrót, ponieważ to oni też decydowali o notacji tablicowej. Nie wiemy nawet, czy uważali, że nawiasy klamrowe to „mniej ważna” opcja, może bardziej lubili nawiasy klamrowe.
thorsten müller

3
@gnat: Kwadratowe nawiasy klamrowe są łatwiejsze do wpisywania na nowoczesnych klawiaturach, czy to dotyczy klawiatur, które były w okolicy, gdy Unix ic był wdrażany po raz pierwszy? Nie mam powodu podejrzewać, że używają tej samej klawiatury lub że zakładaliby, że inne klawiatury będą podobne do ich klawiatur, lub że sądzą, że szybkość pisania warto zoptymalizować za pomocą jednego znaku.
Michael Shaw

1
Ponadto prawo Zipfa jest uogólnieniem tego, co dzieje się w językach naturalnych. C został sztucznie skonstruowany, więc nie ma powodu, aby sądzić, że miałby on zastosowanie tutaj, chyba że projektanci C świadomie zdecydują się go celowo zastosować. Jeśli tak, nie ma powodu zakładać, że uprościłoby to coś tak krótkiego jak pojedynczy znak.
Michael Shaw

1
@gnat FWIW, grep -Fomówi mi *.cpliki kodu źródłowego CPython (rev. 4b42d7f288c5, ponieważ to właśnie mam pod ręką), który zawiera libffi, zawiera 39511 {(39508 {, nie wiem dlaczego dwa nawiasy klamrowe nie są zamknięte), ale tylko 13718 [(13702 [). To zliczanie wystąpień w ciągach znaków i kontekstach niezwiązanych z tym pytaniem, więc nie jest to tak naprawdę dokładne, nawet jeśli zignorujemy to, że podstawa kodu może nie być reprezentatywna (zauważ, że to odchylenie może iść w dowolnym kierunku). Nadal współczynnik 2,8?

1

C (a następnie C ++ i C #) odziedziczył swój styl wzmacniający po swoim poprzedniku B , który został napisany przez Kena Thompsona (z udziałem Dennisa Ritchiego) w 1969 roku.

Ten przykład pochodzi z odniesienia użytkownika do B autorstwa Kena Thompsona (za pośrednictwem Wikipedii ):

/* The following function will print a non-negative number, n, to
   the base b, where 2<=b<=10,  This routine uses the fact that
   in the ASCII character set, the digits 0 to 9 have sequential
   code values.  */

printn(n,b) {
        extern putchar;
        auto a;

        if(a=n/b) /* assignment, not test for equality */
                printn(a, b); /* recursive */
        putchar(n%b + '0');
}

Sam B był ponownie oparty na BCPL , języku napisanym przez Martina Richardsa w 1966 roku dla systemu operacyjnego Multics. System stężeń B wykorzystywał tylko okrągłe stężenia, modyfikowane dodatkowymi znakami (przykład czynnikowy wydruku Martin Richards, z Wikipedii ):

GET "LIBHDR"

LET START() = VALOF $(
        FOR I = 1 TO 5 DO
                WRITEF("%N! = %I4*N", I, FACT(I))
        RESULTIS 0
)$

AND FACT(N) = N = 0 -> 1, N * FACT(N - 1)

Nawiasy klamrowe używane w języku B i kolejnych językach „{...}” to ulepszenie wprowadzone przez Kena Thompsona w stosunku do oryginalnego stylu złożonego aparatu klamrowego w BCPL „$ (...) $”.


1
Nie. Wygląda na to, że Bob Bemer ( en.wikipedia.org/wiki/Bob_Bemer ) jest za to odpowiedzialny - „... zaproponowałeś, aby można je było stosować zamiast słów kluczowych„ początek ”i„ koniec ”Algolu, co jest dokładnie jak zostały później użyte w języku C. ” (z bobbemer.com/BRACES.HTM )
SChepurin

1
$( ... $)Format jest równoważne { ... }w lexer w BCPL, tak jak ??< ... ??>jest równoważne { ... }w C. Poprawa pomiędzy tymi dwoma stylami jest w sprzęt klawiatura - nie jest językiem.
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.