Por que linguagens de programação, especialmente C, usam chaves curvas e não quadradas?

96

A definição de "linguagem C-Style" pode ser praticamente simplificada para "usa chaves ( {})." Por que usamos esse caractere em particular (e por que não algo mais razoável, como [], que não requer a tecla Shift pelo menos nos teclados dos EUA)?

Existe algum benefício real na produtividade do programador proveniente desses aparelhos, ou os novos designers de linguagem devem procurar alternativas (por exemplo, os caras por trás do Python)?

A Wikipedia nos diz que C usa os aparelhos mencionados, mas não o porquê. Uma declaração no artigo da Wikipedia sobre a lista de linguagens de programação baseadas em C sugere que esse elemento de sintaxe é algo especial:

Em termos gerais, os idiomas da família C são aqueles que usam sintaxe de bloco do tipo C (incluindo chaves para iniciar e finalizar o bloco) ...

SomeKittens
fonte
35
A única pessoa que pode responder a isso é Dennis Ritchie e ele está morto. Um palpite razoável é que [] já foram usados ​​para matrizes.
Dirk Holsopple
2
@DirkHolsopple Então ele não deixou nenhum raciocínio para trás? Drat. Além disso: duas votações negativas sobre algo que estou genuinamente curioso? Obrigado rapazes ....
SomeKittens
1
Por favor, continue a discussão sobre esta questão nesta questão meta .
Thomas Owens
2
Eu desbloquei este post. Por favor, mantenha quaisquer comentários sobre a pergunta e discussão sobre adequação na questão Meta .
Thomas Owens
5
Provavelmente, isso também tem algo a ver com o fato de os chavetas serem usadas na notação de conjuntos na matemática, tornando-os um pouco difíceis de usar para acesso a elementos de matriz, em vez de declarar coisas "set" - como estruturas, matrizes, etc. Até linguagens modernas como Python usam chaves para declarar conjuntos e dicionários. A questão então é: por que C também usou chaves para declarar escopo? Provavelmente porque os designers simplesmente não gostaram das alternativas conhecidas, como BEGIN / END, e sobrecarregar a notação de acesso à matriz ([]) foi considerada menos esteticamente correta do que a notação de conjunto.
Charles Salvia

Respostas:

102

Duas das principais influências para C foram a família de idiomas Algol (Algol 60 e Algol 68) e BCPL (da qual C leva seu nome).

O BCPL foi a primeira linguagem de programação entre colchetes, e os colchetes sobreviveram às mudanças sintáticas e se tornaram um meio comum de denotar instruções de código fonte do programa. Na prática, em teclados limitados do dia, os programas de origem costumavam usar as seqüências $ (e $) no lugar dos símbolos {e}. Os comentários de linha única '//' do BCPL, que não foram utilizados em C, reapareceram em C ++ e, posteriormente, em C99.

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

O BCPL introduziu e implementou várias inovações que se tornaram elementos bastante comuns no design de idiomas posteriores. Portanto, foi a primeira linguagem de programação entre parênteses (uma usando {} como delimitadores de bloco) e foi a primeira linguagem a usar // para marcar comentários embutidos.

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

Dentro da BCPL, geralmente se vê chaves, mas nem sempre. Essa era uma limitação dos teclados da época. Os caracteres $(e $)eram lexicograficamente equivalentes a {e }. Dígrafos e trigramas foram mantidos em C (embora um conjunto diferente para substituição de chaves - ??<e ??>).

O uso de chaves foi mais refinado em B (que precedeu C).

Da referência dos usuários ao B de 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');
}

Há indicações de que os aparelhos foram usados ​​como mão curta para begine enddentro de Algol.

Lembro que você também os incluiu no código do cartão de 256 caracteres que você publicou no CACM, porque achei interessante que você propusesse que eles pudessem ser usados ​​no lugar das palavras-chave 'begin' e 'end' do Algol, o que é exatamente como eles foram usados ​​mais tarde na linguagem C.

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


O uso de colchetes (como uma substituição sugerida na pergunta) remonta ainda mais. Como mencionado, a família Algol influenciou C. No Algol 60 e 68 (C foi escrito em 1972 e BCPL em 1966), o colchete foi usado para designar um índice em uma matriz ou matriz.

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

Como os programadores já estavam familiarizados com colchetes para matrizes em Algol e BCPL e chaves para blocos em BCPL, havia pouca necessidade ou desejo de mudar isso ao criar outro idioma.


A pergunta atualizada inclui um adendo de produtividade para o uso de chaves e menciona python. Existem alguns outros recursos que fazem este estudo, embora a resposta se refira a "É anedótico e o que você está acostumado é com o que é mais produtivo". Devido às habilidades amplamente variadas em programação e familiaridade com diferentes idiomas, elas se tornam difíceis de explicar.

Veja também: Estouro de pilha Existem estudos estatísticos que indicam que o Python é "mais produtivo"?

Muitos dos ganhos dependeriam do IDE (ou falta de) usado. Nos editores baseados em vi, colocar o cursor sobre uma abertura / fechamento correspondente e pressionar pressionará %o cursor para o outro caractere correspondente. Isso é muito eficiente nos idiomas baseados em C nos velhos tempos - menos agora.

Uma melhor comparação seria entre {}e begin/ endquais eram as opções do dia (o espaço horizontal era precioso). Muitas línguas Wirth foram baseadas em um begine endestilo (Algol (mencionados acima), pascal (muitos estão familiarizados com), ea família Modula).

Tenho dificuldade em encontrar alguém que isole esse recurso específico de idioma - na melhor das hipóteses, posso mostrar que os idiomas entre chaves são muito mais populares do que os idiomas finais e é uma construção comum. Conforme mencionado no link de Bob Bemer acima, a chave foi usada para facilitar a programação como taquigrafia.

De Por Pascal não é minha linguagem de programação favorita

Os programadores C e Ratfor consideram 'início' e 'final' volumosos em comparação com {e}.

O que é tudo o que pode ser dito - sua familiaridade e preferência.

Comunidade
fonte
14
Agora todo mundo aqui está aprendendo BCPL em vez de trabalhar :)
Denys Séguret
Os trigramas (introduzidos na norma ISO C 1989) para {e }são ??<e ??>. Os digrafos (introduzidos pela emenda de 1995) são <%e %>. Os trígrafos são expandidos em todos os contextos, em uma fase de tradução muito inicial. Os dígrafos são tokens e não são expandidos em literais de seqüência de caracteres, constantes de caracteres ou comentários.
26413 Keith Thompson
Havia algo antes de 1989 para isso em C (eu teria que cavar meu livro da primeira edição para obter uma data sobre isso). Nem todas as páginas de código EBCDIC tinham chaves (ou colchetes), e havia provisões para isso nos primeiros compiladores C.
O @NevilleDNZ BCPL usou chaves em 1966. Onde Algol68 teve sua noção seria algo para explorar - mas a BCPL não conseguiu com Algo68. O operador ternário é algo em que tenho interesse e o rastreei até a CPL (1963) (a antecessora da BCPL), que emprestou a noção de Lisp (1958).
1968: Algol68 permite redondos suportes (~) como uma forma abreviada de começar ~ finais negrito blocos de símbolos. Estes são chamados símbolos breves , cf wp: Algol68 Símbolos em negrito , isso permite que blocos de código sejam tratados como expressões . A68 também possui breves taquigrafia como C's ?: Operador ternário, por exemplo, em x:=(c|s1|s2)vez de C's x=c?s1|s2. Da mesma forma, isso se aplica às declarações if & case . ¢ BTW: A68 é de onde o shell conseguiu o seu esac & fi ¢
NevilleDNZ
24

Colchetes []são mais fáceis de escrever, desde IBM 2741 terminal que foi "amplamente utilizado em Multics" OS, que por sua vez tinha Dennis Ritchie, um dos criadores da linguagem C como membro da equipe dev .

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

Observe a ausência de chaves no layout do IBM 2741!

Em C, chaves entre colchetes são "tiradas", pois são usadas para matrizes e ponteiros . Se os projetistas de linguagem esperassem que matrizes e ponteiros fossem mais importantes / usados ​​com mais freqüência do que blocos de código (o que soa como uma suposição razoável ao seu lado, mais no contexto histórico do estilo de codificação abaixo), isso significaria que chaves entre chaves seriam "menos importantes" "sintaxe.

A importância das matrizes é bastante aparente no artigo O desenvolvimento da linguagem C de Ritchie. Existe até uma suposição explicitamente declarada de "prevalência de indicadores em programas C" .

... nova linguagem retinha uma explicação coerente e viável (se não usual) da semântica de matrizes ... Duas idéias são as mais características de C entre os idiomas de sua classe: a relação entre matrizes e ponteiros ... A outra característica de C, seu tratamento de matrizes ... tem virtudes reais . Embora a relação entre ponteiros e matrizes seja incomum, ela pode ser aprendida. Além disso, a linguagem mostra um poder considerável para descrever conceitos importantes, por exemplo, vetores cuja duração varia em tempo de execução, com apenas algumas regras e convenções básicas ...


Para uma melhor compreensão do contexto histórico e do estilo de codificação da época em que a linguagem C foi criada, é necessário levar em consideração que "a origem de C está intimamente ligada ao desenvolvimento do Unix" e, especificamente, que portar o SO para um PDP- 11 "levou ao desenvolvimento de uma versão inicial do C" ( fonte de citações ). Segundo a Wikipedia , "em 1972, o Unix foi reescrito na linguagem de programação C" .

O código fonte de várias versões antigas do Unix está disponível online, por exemplo, no site da árvore Unix . Das várias versões apresentadas lá, a mais relevante parece ser a Segunda Edição Unix, datada de 1972-06:

A segunda edição do Unix foi desenvolvida para o PDP-11 no Bell Labs por Ken Thompson, Dennis Ritchie e outros. Ele estendeu a Primeira Edição com mais chamadas do sistema e mais comandos. Esta edição também viu o início da linguagem C, que foi usada para escrever alguns dos comandos ...

Você pode procurar e estudar o código fonte C na página Second Edition Unix (V2) para ter uma idéia do estilo de codificação típico da época.

Um exemplo proeminente que apóia a idéia de que naquela época era bastante importante que o programador pudesse digitar colchetes com facilidade pode ser encontrado no código-fonte 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 observar como a motivação pragmática de escolher caracteres para denotar elementos de sintaxe da linguagem com base em seu uso em aplicações práticas direcionadas se assemelha à Lei de Zipf, conforme explicado nesta resposta fantástica ...

A relação observada entre frequência e duração é chamada Lei de Zipf

... com a única diferença de que o comprimento na instrução acima é substituído por / generalizado como velocidade de digitação.

mosquito
fonte
5
Alguma coisa que apóie essa expectativa "aparente" dos designers de linguagem? Não é preciso muita programação em C para perceber que chaves entre colchetes são muito mais comuns que declarações de matriz. Isso realmente não mudou muito desde os tempos antigos - veja a K&R.
1
De alguma forma, duvido dessa explicação. Não sabemos qual é o esperado e eles poderiam ter escolhido o contrário facilmente, já que eles também decidiram sobre a notação de matriz. Nem sabemos se eles achavam que o aparelho era a opção "menos importante", talvez eles gostassem mais do aparelho.
26613 Thorsten Müller
3
@gnat: colchetes são mais fáceis de digitar em teclados modernos, isso se aplica aos teclados existentes quando o unix ec estava sendo implementado pela primeira vez? Não tenho motivos para suspeitar que eles estejam usando o mesmo teclado, ou que eles suponham que outros teclados sejam como os teclados ou que pensariam que a velocidade de digitação valeria a pena otimizar por um caractere.
26613 Michael Shaw
1
Além disso, a lei de Zipf é uma generalização do que acaba acontecendo nas línguas naturais. C foi artificialmente construído, então não há razão para pensar que se aplicaria aqui, a menos que os projetistas de C decidissem conscientemente aplicá-lo deliberadamente. Se aplicável, não há razão para supor que isso simplificaria algo já tão curto quanto um único personagem.
Michael Shaw
1
@gnat FWIW, grep -Fodiz-me os *.carquivos do código-fonte CPython (rev. 4b42d7f288c5 porque é isso que tenho em mãos), que inclui a libffi, contém 39511 {(39508 {, não sei por que duas chaves não estão fechadas), mas apenas 13718 [(13702 [) Isso está contando ocorrências em seqüências de caracteres e em contextos não relacionados a essa pergunta, portanto isso não é realmente preciso, mesmo se ignorarmos que a base de código pode não ser representativa (observe que esse viés pode ir em qualquer direção). Ainda assim, um fator de 2,8?
1

C (e subsequentemente C ++ e C #) herdou seu estilo de suporte do antecessor B , que foi escrito por Ken Thompson (com contribuições de Dennis Ritchie) em 1969.

Este exemplo é da referência dos usuários a B de Ken Thompson (via 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');
}

O próprio B foi novamente baseado no BCPL , uma linguagem escrita por Martin Richards em 1966 para o sistema operacional Multics. O sistema de órtese de B usava apenas chavetas, modificadas por caracteres adicionais (exemplo de fatorial de impressão de 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)

Os chavetas usadas em B e nos idiomas subsequentes "{...}" são uma melhoria que Ken Thompson fez sobre o estilo de chaveta composto original em BCPL "$ (...) $".

Profeta
fonte
1
Não. Parece que Bob Bemer ( en.wikipedia.org/wiki/Bob_Bemer ) é responsável por isso - "... você propôs que eles pudessem ser usados ​​no lugar das palavras-chave Algol 'begin' e 'end', o que é exatamente como eles foram usados ​​mais tarde na linguagem C. " (from bobbemer.com/BRACES.HTM )
SChepurin
1
O $( ... $)formato é equivalente a { ... }no lexer em BCPL, assim como ??< ... ??>é equivalente a { ... }em C. A melhora entre os dois estilos é no hardware do teclado - e não a linguagem.