Perché i linguaggi di programmazione, specialmente C, usano parentesi graffe e non quadrate?


96

La definizione di "linguaggio C-Style" può praticamente essere semplificata fino a "usa parentesi graffe ( {})". Perché usiamo quel particolare carattere (e perché non qualcosa di più ragionevole, come [], che non richiede il tasto shift almeno sulle tastiere statunitensi)?

C'è qualche vantaggio reale per la produttività del programmatore che deriva da queste parentesi graffe o i nuovi designer linguistici dovrebbero cercare alternative (ad esempio i ragazzi dietro Python)?

Wikipedia ci dice che C usa dette parentesi graffe, ma non il perché. Una dichiarazione nell'articolo di Wikipedia sull'elenco dei linguaggi di programmazione basati su C suggerisce che questo elemento di sintassi è in qualche modo speciale:

In generale, i linguaggi della famiglia C sono quelli che usano la sintassi del blocco simile al C (incluse le parentesi graffe per iniziare e terminare il blocco) ...


35
L'unica persona che può rispondere è Dennis Ritchie ed è morto. Un'ipotesi ragionevole è che [] erano già stati presi per gli array.
Dirk Holsopple,

2
@DirkHolsopple Quindi non ha lasciato alcun ragionamento? Drat. Inoltre: due voti negativi su qualcosa di cui sono sinceramente curioso? Grazie ragazzi ....
SomeKittens,

1
Continua la discussione su questa domanda in questa Meta domanda .
Thomas Owens

2
Ho sbloccato questo post. Conservare eventuali commenti sulla domanda e la discussione sull'adeguatezza della domanda Meta .
Thomas Owens

5
Probabilmente ha anche qualcosa a che fare con il fatto che le parentesi graffe sono usate nella notazione di insiemi in matematica, rendendole un po 'imbarazzanti da usare per l'accesso agli elementi dell'array, piuttosto che cose come dichiarare cose "set" -ish come strutture, array, ecc. Anche i linguaggi moderni come Python usano parentesi graffe per dichiarare set e dizionari. La domanda quindi, è perché C ha usato anche parentesi graffe per dichiarare l'ambito? Probabilmente perché ai progettisti non sono piaciute le alternative conosciute, come BEGIN / END, e l'overloading notation di accesso all'array ([]) è stato considerato meno esteticamente valido rispetto alla notazione impostata.
Charles Salvia,

Risposte:


102

Due delle principali influenze su C furono la famiglia di lingue Algol (Algol 60 e Algol 68) e BCPL (da cui C prende il nome).

BCPL è stato il primo linguaggio di programmazione per parentesi graffe e le parentesi graffe sono sopravvissute ai cambiamenti sintattici e sono diventate un mezzo comune per indicare le istruzioni del codice sorgente del programma. In pratica, su tastiere limitate del giorno, i programmi sorgente spesso utilizzavano le sequenze $ (e $) al posto dei simboli {e}. I commenti "//" a riga singola di BCPL, che non sono stati ripresi in C, sono riapparsi in C ++ e successivamente in C99.

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

BCPL ha introdotto e implementato diverse innovazioni che sono diventate elementi abbastanza comuni nella progettazione delle lingue successive. Pertanto, è stato il primo linguaggio di programmazione a parentesi graffa (uno che utilizza {} come delimitatori di blocchi) ed è stato il primo linguaggio a utilizzare // per contrassegnare i commenti incorporati.

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

All'interno di BCPL, si vedono spesso parentesi graffe, ma non sempre. Questa era una limitazione delle tastiere al momento. I personaggi $(e $)sono stati lessicograficamente equivalenti a {e }. Digraph e trigraphs sono stati mantenuti in C (anche se un set diverso per la sostituzione di parentesi graffe - ??<e ??>).

L'uso di parentesi graffe è stato ulteriormente perfezionato in B (che ha preceduto C).

Dal riferimento degli utenti a B di Ken Thompson:

/* 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');
}

Ci sono indicazioni che le parentesi graffe siano state usate come abbreviazione per begine endall'interno dell'Algol.

Ricordo che li hai inclusi anche nel codice della carta di 256 caratteri che hai pubblicato in CACM, perché ho trovato interessante che tu abbia proposto di usarli al posto delle parole chiave Algol "inizio" e "fine", che è esattamente come sono stati successivamente utilizzati nel linguaggio C.

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


L'uso di parentesi quadre (come suggerito come sostituto nella domanda) risale ancora più indietro. Come accennato, la famiglia Algol ha influenzato C. All'interno di Algol 60 e 68 (C è stata scritta nel 1972 e BCPL nel 1966), la parentesi quadra è stata utilizzata per designare un indice in una matrice o matrice.

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

Dato che i programmatori avevano già familiarità con le parentesi quadre per gli array in Algol e BCPL e le parentesi graffe per i blocchi in BCPL, c'era poco bisogno o desiderio di cambiare questo quando si creava un'altra lingua.


La domanda aggiornata include un addendum di produttività per l'utilizzo di parentesi graffe e menziona Python. Ci sono altre risorse che fanno questo studio sebbene la risposta si riduca a "È aneddotica, e ciò a cui sei abituato è ciò con cui sei più produttivo". A causa delle diverse competenze nella programmazione e nella familiarità con lingue diverse, queste diventano difficili da spiegare.

Vedi anche: Stack Overflow Esistono studi statistici che indicano che Python è "più produttivo"?

Gran parte dei guadagni dipenderebbe dall'IDE (o dalla mancanza di) utilizzato. Negli editor basati su vi, posizionando il cursore su una corrispondenza aperta / chiusa e premendo %si sposta il cursore sull'altro carattere corrispondente. Questo è molto efficiente con i linguaggi basati su C ai vecchi tempi - meno ora.

Un confronto migliore sarebbe tra {}e begin/ endquali erano le opzioni del giorno (lo spazio orizzontale era prezioso). Molte lingue di Wirth erano basate su un begine endstile (Algol (menzionato sopra), pascal (molti hanno familiarità con) e la famiglia Modula).

Ho difficoltà a trovare qualcuno che isola questa caratteristica linguistica specifica - nella migliore delle ipotesi, posso dimostrare che le lingue delle parentesi graffe sono molto più popolari delle lingue iniziali e che è un costrutto comune. Come menzionato nel link Bob Bemer sopra, la parentesi graffa è stata utilizzata per facilitare la programmazione come stenografia.

Da perché Pascal non è il mio linguaggio di programmazione preferito

I programmatori C e Ratfor trovano 'inizio' e 'fine' ingombranti rispetto a {e}.

Il che riguarda tutto ciò che si può dire: la sua familiarità e preferenza.


14
Ora tutti qui stanno imparando BCPL invece di lavorare :)
Denys Séguret,

Le trigrafi (introdotte nella norma ISO C del 1989) per {e }sono ??<e ??>. I digrafi (introdotti dall'emendamento del 1995) sono <%e %>. Le trigrafi si espandono in tutti i contesti, in una fase di traduzione molto precoce. I digraph sono token e non vengono espansi in valori letterali di stringa, costanti di carattere o commenti.
Keith Thompson,

Esisteva qualcosa prima del 1989 per questo in C (dovrei scavare il mio libro della prima edizione per avere un appuntamento al riguardo). Non tutte le pagine di codice EBCDIC avevano una parentesi graffa (o parentesi quadre), e c'erano disposizioni per questo nei primi compilatori C.

@NevilleDNZ BCPL usò le parentesi graffe nel 1966. Da dove Algol68 prese le sue idee sarebbe qualcosa da esplorare - ma BCPL non le ottenne da Algo68. L'operatore ternario è qualcosa che mi interessava e l'ho rintracciato in CPL (1963) (il predecessore di BCPL) che prese in prestito l'idea di Lisp (1958).

1968: Algol68 consente le parentesi tonde (~) come abbreviazione di blocchi di simboli in grassetto inizio ~ fine . Questi sono chiamati simboli brevi , cfr wp: Algol68 Simboli in grassetto , questo consente di trattare blocchi di codice come espressioni . A68 ha anche brevi abbreviazioni come di C :? Operatore ternario ad esempio, invece di C di . Allo stesso modo questo vale per le dichiarazioni if & case . ¢ A proposito: A68 è da dove la shell ha ottenuto il suo esac & fi ¢x:=(c|s1|s2)x=c?s1|s2
NevilleDNZ

24

Le parentesi quadre []sono più facili da digitare, sin dal terminale IBM 2741 che era "ampiamente utilizzato sul sistema operativo Multics" , che a sua volta aveva Dennis Ritchie, uno dei creatori del linguaggio C come membro del team di sviluppo .

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

Nota l' assenza di parentesi graffe nel layout IBM 2741!

In C, le parentesi quadre vengono "prese" in quanto utilizzate per matrici e puntatori . Se i progettisti del linguaggio si aspettassero che gli array e i puntatori fossero più importanti / usati più frequentemente dei blocchi di codice (che suona come un presupposto ragionevole al loro fianco, più sul contesto storico dello stile di codifica di seguito), ciò significherebbe che le parentesi graffe passerebbero a "meno importante "sintassi.

L'importanza delle matrici è piuttosto evidente nell'articolo The Development of the C Language di Ritchie. C'è persino un'ipotesi esplicita di "prevalenza dei puntatori nei programmi C" .

... il nuovo linguaggio ha mantenuto una spiegazione coerente e praticabile (se inusuale) della semantica delle matrici ... Due idee sono le più caratteristiche di C tra le lingue della sua classe: la relazione tra matrici e puntatori ... L'altra caratteristica di C, il suo trattamento degli array ... ha delle vere virtù . Sebbene la relazione tra puntatori e array sia insolita, può essere appresa. Inoltre, il linguaggio mostra un notevole potere nel descrivere concetti importanti, ad esempio vettori la cui lunghezza varia in fase di esecuzione, con solo alcune regole e convenzioni di base ...


Per un'ulteriore comprensione del contesto storico e dello stile di codifica del tempo in cui è stato creato il linguaggio C, è necessario tenere conto del fatto che "l'origine di C è strettamente legata allo sviluppo di Unix" e, in particolare, che porta l'OS su un PDP- 11 "ha portato allo sviluppo di una prima versione di C" ( fonte delle citazioni ). Secondo Wikipedia , "nel 1972, Unix è stato riscritto nel linguaggio di programmazione C" .

Il codice sorgente di varie vecchie versioni di Unix è disponibile online, ad esempio sul sito The Unix Tree . Delle varie versioni presentate lì, la più rilevante sembra essere la Seconda Edizione Unix del 1972-06:

La seconda edizione di Unix è stata sviluppata per il PDP-11 ai Bell Labs da Ken Thompson, Dennis Ritchie e altri. Ha esteso la prima edizione con più chiamate di sistema e più comandi. Questa edizione ha visto anche l'inizio del linguaggio C, che è stato usato per scrivere alcuni dei comandi ...

Puoi sfogliare e studiare il codice sorgente C dalla pagina Unix (V2) della Seconda Edizione per avere un'idea del tipico stile di codifica del tempo.

Un esempio di spicco che supporta l'idea che all'epoca fosse piuttosto importante per il programmatore poter digitare facilmente parentesi quadre si trova nel codice sorgente 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;

È interessante notare come la motivazione pragmatica della scelta dei caratteri per indicare elementi di sintassi del linguaggio basati sul loro uso in applicazioni pratiche mirate assomigli alla Legge di Zipf come spiegato in questa formidabile risposta ...

la relazione osservata tra frequenza e lunghezza è chiamata Legge di Zipf

... con la sola differenza che la lunghezza nell'istruzione precedente è sostituita da / generalizzata come velocità di digitazione.


5
Qualcosa a sostegno di questa "apparente" aspettativa da parte dei progettisti del linguaggio? Non ci vuole molta programmazione in C per notare che le parentesi graffe sono molto più comuni delle dichiarazioni di array. Questo non è cambiato molto dai tempi antichi: dai un'occhiata a K&R.

1
Dubito in qualche modo di questa spiegazione. Non sappiamo quale fosse l'atteso e avrebbero potuto facilmente sceglierlo al contrario poiché erano le persone a decidere anche sulla notazione di array. Non sappiamo nemmeno se pensavano che le parentesi graffe fossero l'opzione "meno importante", forse gli piacevano di più le parentesi graffe.
Thorsten Müller,

3
@gnat: le parentesi quadre sono più facili da digitare sulle tastiere moderne, questo vale per le tastiere che erano in giro quando unix e c sono state implementate per la prima volta? Non ho motivo di sospettare che stessero usando la stessa tastiera, o che presumessero che le altre tastiere sarebbero come le loro tastiere, o che avrebbero pensato che la velocità di battitura sarebbe valsa la pena di essere ottimizzata da un carattere.
Michael Shaw,

1
Inoltre, la legge di Zipf è una generalizzazione di ciò che finisce per accadere nei linguaggi naturali. C è stato costruito artificialmente, quindi non c'è motivo di pensare che si applicherebbe qui a meno che i progettisti di C non abbiano deliberatamente deciso di applicarlo deliberatamente. Se fosse applicabile, non c'è motivo di presumere che semplificherebbe qualcosa di già breve come un singolo personaggio.
Michael Shaw,

1
@gnat FWIW, grep -Fomi dice i *.cfile del codice sorgente di CPython (rev. 4b42d7f288c5 perché è quello che ho a portata di mano), che include libffi, contiene 39511 {(39508 {, non so perché due parentesi graffe non sono chiuse), ma solo 13718 [(13702 [). Questo è il conteggio delle occorrenze nelle stringhe e nei contesti non correlati a questa domanda, quindi questo non è molto accurato, anche se ignoriamo che la base di codice potrebbe non essere rappresentativa (si noti che questo errore potrebbe andare in entrambe le direzioni). Tuttavia, un fattore 2,8?

1

C (e successivamente C ++ e C #) ereditarono il suo stile di rinforzo dal suo predecessore B , che fu scritto da Ken Thompson (con contributi di Dennis Ritchie) nel 1969.

Questo esempio è tratto dal riferimento dell'utente a B di Ken Thompson (tramite Wikipedia ):

/* 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');
}

B stesso era di nuovo basato su BCPL , una lingua scritta da Martin Richards nel 1966 per il sistema operativo Multics. Il sistema di controventatura di B utilizzava solo parentesi tonde, modificate da caratteri aggiuntivi (Stampa esempio fattoriale di Martin Richards, via Wikipedia ):

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)

Le parentesi graffe utilizzate in B e nelle lingue successive "{...}" sono un miglioramento apportato da Ken Thompson rispetto allo stile originale di controvento composto in BCPL "$ (...) $".


1
No. Sembra che Bob Bemer ( en.wikipedia.org/wiki/Bob_Bemer ) sia responsabile di questo - "... hai proposto di usarli al posto delle parole chiave" inizio "e" fine "dell'Algol, che è esattamente come sono stati successivamente utilizzati nel linguaggio C. " (da bobbemer.com/BRACES.HTM )
SChepurin,

1
Il $( ... $)formato è equivalente { ... }nel lexer in BCPL, proprio come ??< ... ??>è equivalente a { ... }in C. Il miglioramento tra i due stili è nell'hardware della tastiera - non la lingua.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.