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

93

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

C'è qualche effettivo beneficio per la produttività dei programmatori che deriva da queste parentesi, o i nuovi progettisti di linguaggi dovrebbero cercare alternative (cioè i ragazzi dietro a Python)?

Wikipedia ci dice che C utilizza le parentesi graffe, ma non perché . Una dichiarazione nell'articolo di Wikipedia sulla Lista dei linguaggi di programmazione basati su C suggerisce che questo elemento di sintassi è in qualche modo speciale:

Broadly speaking, C-family languages are those that use C-like block syntax (including curly braces to begin and end the block)...

    
posta SomeKittens 26.02.2013 - 15:59
fonte

3 risposte

102

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

BCPL was the first curly bracket programming language, and the curly brackets survived the syntactical changes and have become a common means of denoting program source code statements. In practice, on limited keyboards of the day, source programs often used the sequences $( and $) in place of the symbols { and }. The single-line '//' comments of BCPL, which were not taken up in C, reappeared in C++, and later in C99.

Da link

BCPL introduced and implemented several innovations which became quite common elements in the design of later languages. Thus, it was the first curly bracket programming language (one using { } as block delimiters), and it was the first language to use // to mark inline comments.

Da link

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

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

Da 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 sono state utilizzate come mano corta per begin e end per Algol.

I remember that you also included them in the 256-character card code that you published in CACM, because I found it interesting that you proposed that they could be used in place of the Algol 'begin' and 'end' keywords, which is exactly how they were later used in the C language.

Da link

L'uso di parentesi quadre (come una sostituzione suggerita nella domanda) risale ancora più lontano. Come accennato, la famiglia Algol influenzò C. Entro Algol 60 e 68 (C fu scritta nel 1972 e BCPL nel 1966), la parentesi quadra fu usata 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.

Poiché i programmatori conoscevano già 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 cambiarlo quando si creava un'altra lingua.

La domanda aggiornata include un addendum di produttività per l'utilizzo delle parentesi graffe e menziona python. Ci sono altre risorse che fanno questo studio, anche se la risposta si riduce a "Il suo aneddotico, e ciò a cui sei abituato è quello con cui sei più produttivo." A causa delle diverse abilità di programmazione e familiarità con le diverse lingue, queste diventano difficili da spiegare.

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

Gran parte dei guadagni dipenderebbe dall'IDE (o dalla mancanza di) utilizzata. Negli editor basati su vi, posizionando il cursore su una apertura / chiusura corrispondenti e premendo % , il cursore si sposta sull'altro carattere corrispondente. Questo è molto efficace con i linguaggi basati su C ai vecchi tempi - meno adesso.

Un confronto migliore sarebbe tra {} e begin / end che erano le opzioni del giorno (lo spazio orizzontale era prezioso). Molti linguaggi Wirth erano basati su uno stile begin e end (Algol (menzionato sopra), pascal (molti hanno familiarità con) e la famiglia Modula).

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

Da Perché Pascal non è la mia lingua di programmazione preferita

C and Ratfor programmers find 'begin' and 'end' bulky compared to { and }.

Che riguarda tutto ciò che si può dire - la sua familiarità e preferenza.

    
risposta data 26.02.2013 - 16:29
fonte
24

Le graffe quadrate [] sono più facili da scrivere, dal momento che il IBM 2741 che era "ampiamente usato su Multics" OS, che a sua volta aveva Dennis Ritchie , uno dei creatori di linguaggi C come membro del team di sviluppo .

Notare assenza di parentesi graffe sul layout IBM 2741!

In C, le parentesi quadre sono "prese" come sono usate per array e puntatori . Se i progettisti di linguaggi si aspettavano che gli array e i puntatori fossero più importanti / usati più frequentemente dei blocchi di codice (che sembra un supposizione ragionevole al loro fianco, più sul contesto storico dello stile di codifica sottostante), ciò significherebbe che le parentesi graffe passerebbero alla sintassi" meno importante ".

L'importanza degli array è piuttosto evidente nell'articolo Lo sviluppo del linguaggio C di Ritchie. C'è anche un'assunzione esplicita di "prevalenza di puntatori nei programmi C" .

...new language retained a coherent and workable (if unusual) explanation of the semantics of arrays... Two ideas are most characteristic of C among languages of its class: the relationship between arrays and pointers... The other characteristic feature of C, its treatment of arrays... has real virtues. Although the relationship between pointers and arrays is unusual, it can be learned. Moreover, the language shows considerable power to describe important concepts, for example, vectors whose length varies at run time, with only a few basic rules and conventions...

Per un'ulteriore comprensione del contesto storico e dello stile di codifica del tempo in cui è stato creato il linguaggio C, è necessario tener conto del fatto che "l'origine di C è strettamente legata allo sviluppo di Unix" e , in particolare, che il porting del sistema operativo su un PDP-11 "ha portato allo sviluppo di una versione precedente di C" ( cita la fonte ). 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 nel sito The Unix Tree . Di varie versioni presentate qui, la più rilevante sembra essere Second Edition Unix del 1972-06 :

The second edition of Unix was developed for the PDP-11 at Bell Labs by Ken Thompson, Dennis Ritchie and others. It extended the First Edition with more system calls and more commands. This edition also saw the beginning of the C language, which was used to write some of the commands...

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

Un esempio importante che supporta l'idea che a quei tempi era piuttosto importante che il programmatore fosse in grado di digitare parentesi quadre con facilità può essere trovato in V2 / c / ncc.c codice sorgente:

/* 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++ == '
/* 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++ == '%pre%') return (0);
ll:;
    }
    return(1);
}

tsp;
tmp0;
tmp1;
tmp2;
tmp3;
') return (0); ll:; } return(1); } tsp; tmp0; tmp1; tmp2; tmp3;

È interessante notare come la motivazione pragmatica di scegliere i caratteri per indicare gli elementi di sintassi del linguaggio basati sul loro uso in applicazioni pratiche mirate assomigli alla legge di Zipf come spiegato in questa magnifica risposta ...

observed relationship between frequency and length is called Zipf's Law

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

    
risposta data 26.02.2013 - 16:05
fonte
1

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

Questo esempio è tratto dal Riferimento degli utenti 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 rinforzo di B utilizzava solo parentesi tonde, modificate da caratteri aggiuntivi (esempio fattoriale di stampa di Martin Richards, tramite 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 usate in B e nelle lingue successive "{...}" sono un miglioramento che Ken Thompson ha realizzato rispetto allo stile di parentesi graffa originale in BCPL "$ (...) $".

    
risposta data 26.02.2013 - 16:34
fonte

Leggi altre domande sui tag