¿Por qué los lenguajes de programación, especialmente C, usan llaves y no cuadrados?

96

La definición de "lenguaje C-Style" prácticamente se puede simplificar a "usa llaves ( {})". ¿Por qué usamos ese carácter en particular (y por qué no algo más razonable, como []que no requiere la tecla Mayús al menos en los teclados de EE. UU.)?

¿Existe algún beneficio real para la productividad del programador que provenga de estos aparatos, o deberían los nuevos diseñadores de lenguaje buscar alternativas (es decir, los chicos detrás de Python)?

Wikipedia nos dice que C usa dichos aparatos ortopédicos, pero no por qué. Una declaración en el artículo de Wikipedia sobre la Lista de lenguajes de programación basados ​​en C sugiere que este elemento de sintaxis es algo especial:

En términos generales, los lenguajes de la familia C son aquellos que usan la sintaxis de bloque tipo C (incluidas las llaves para comenzar y finalizar el bloque) ...

Algunos gatitos
fuente
35
La única persona que puede responder esto es Dennis Ritchie y está muerto. Una suposición razonable es que [] ya se tomaron para matrices.
Dirk Holsopple
2
@DirkHolsopple ¿Entonces no dejó ningún razonamiento? Drat Además: ¿dos votos negativos sobre algo que realmente me interesa? Gracias chicos ...
SomeKittens
1
Continúe la discusión sobre esta pregunta en esta Meta pregunta .
Thomas Owens
2
He desbloqueado esta publicación. Mantenga cualquier comentario sobre la pregunta y discusión sobre la adecuación de la pregunta Meta .
Thomas Owens
55
Probablemente también tenga algo que ver con el hecho de que las llaves se usan en la notación de conjuntos en matemáticas, lo que las hace un tanto incómodas de usar para el acceso a elementos de la matriz, en lugar de cosas como declarar cosas de "conjunto" como estructuras, matrices, etc. Incluso los lenguajes modernos como Python usan llaves para declarar conjuntos y diccionarios. La pregunta, entonces, es ¿por qué C también usó llaves para declarar el alcance? Probablemente porque a los diseñadores simplemente no les gustaron las alternativas conocidas, como BEGIN / END, y sobrecargar la notación de acceso a la matriz ([]) se consideró menos estéticamente sólida que la notación establecida.
Charles Salvia

Respuestas:

102

Dos de las principales influencias para C fueron la familia de lenguajes Algol (Algol 60 y Algol 68) y BCPL (de donde C toma su nombre).

BCPL fue el primer lenguaje de programación de llaves, y las llaves sobrevivieron a los cambios sintácticos y se han convertido en un medio común para denotar declaraciones de código fuente del programa. En la práctica, en teclados limitados del día, los programas fuente a menudo usaban las secuencias $ (y $) en lugar de los símbolos {y}. Los comentarios de una sola línea '//' de BCPL, que no se tomaron en C, reaparecieron en C ++ y más tarde en C99.

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

BCPL introdujo e implementó varias innovaciones que se convirtieron en elementos bastante comunes en el diseño de lenguajes posteriores. Por lo tanto, fue el primer lenguaje de programación de llaves (uno que usa {} como delimitadores de bloque), y fue el primer lenguaje en usar // para marcar comentarios en línea.

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

Dentro de BCPL, a menudo se ven llaves, pero no siempre. Esta era una limitación de los teclados en ese momento. Los caracteres $(y $)eran lexicográficamente equivalentes a {y }. Los dígrafos y los trigrafos se mantuvieron en C (aunque un conjunto diferente para el reemplazo de llaves ) ??<y ??>).

El uso de llaves se refinó aún más en B (que precedió a C).

De la referencia de los usuarios a 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');
}

Hay indicios de que las llaves se usaron como mano corta para beginy enddentro de Algol.

Recuerdo que también los incluyó en el código de la tarjeta de 256 caracteres que publicó en CACM, porque me pareció interesante que propusiera que pudieran usarse en lugar de las palabras clave Algol 'comenzar' y 'finalizar', que es exactamente cómo se usaron más tarde en el lenguaje C.

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


El uso de corchetes (como reemplazo sugerido en la pregunta) se remonta aún más. Como se mencionó, la familia Algol influyó en C. Dentro de Algol 60 y 68 (C se escribió en 1972 y BCPL en 1966), el corchete se usó para designar un índice en una matriz o matriz.

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

Como los programadores ya estaban familiarizados con los corchetes para matrices en Algol y BCPL, y las llaves para bloques en BCPL, había poca necesidad o deseo de cambiar esto al hacer otro idioma.


La pregunta actualizada incluye un anexo de productividad para el uso de llaves y menciona python. Hay otros recursos que hacen este estudio, aunque la respuesta se reduce a "Es anecdótico, y a lo que está acostumbrado es con lo que es más productivo". Debido a las habilidades que varían ampliamente en la programación y la familiaridad con los diferentes lenguajes, estos se vuelven difíciles de explicar.

Ver también: Desbordamiento de pila ¿Hay estudios estadísticos que indiquen que Python es "más productivo"?

Gran parte de las ganancias dependerían del IDE (o falta de) que se use. En editores basados ​​en vi, al colocar el cursor sobre una apertura / cierre coincidente y presionar %, el cursor se moverá al otro carácter coincidente. Esto es muy eficiente con lenguajes basados ​​en C en los viejos tiempos, menos ahora.

Una mejor comparación sería entre {}y begin/ endcuáles fueron las opciones del día (el espacio horizontal era precioso). Muchos lenguajes Wirth se basaron en una beginy endestilo (Algol (mencionado anteriormente), Pascal (muchos están familiarizados con), y la familia Modula).

Tengo dificultades para encontrar alguno que aísle esta característica específica del lenguaje; en el mejor de los casos, lo que puedo hacer es demostrar que los lenguajes de llaves son mucho más populares que los idiomas de inicio y final y es una construcción común. Como se mencionó anteriormente en el enlace Bob Bemer, la llave se usó para facilitar la programación como taquigrafía.

Por qué Pascal no es mi lenguaje de programación favorito

Los programadores de C y Ratfor encuentran que 'comenzar' y 'terminar' son voluminosos en comparación con {y}.

Lo cual es todo lo que se puede decir: su familiaridad y preferencia.

Comunidad
fuente
14
Ahora todos aquí están aprendiendo BCPL en lugar de trabajar :)
Denys Séguret
Los trigrafos (introducidos en el estándar ISO C 1989) para {y }son ??<y ??>. Los dígrafos (introducidos por la enmienda de 1995) son <%y %>. Los trígrafos se expanden en todos los contextos, en una fase de traducción muy temprana. Los dígrafos son tokens y no se expanden en literales de cadena, constantes de caracteres o comentarios.
Keith Thompson
Hubo algo antes de 1989 para esto en C (tendría que desenterrar mi primer libro de edición para obtener una fecha al respecto). No todas las páginas de códigos EBCDIC tenían una llave (o corchetes) en ellas, y había disposiciones para esto en los primeros compiladores de C.
@NevilleDNZ BCPL usó llaves en 1966. De dónde sacó Algol68 su idea sería algo para explorar, pero BCPL no lo obtuvo de Algo68. El operador ternario es algo que me ha interesado y lo he rastreado hasta CPL (1963) (el predecesor de BCPL) que tomó prestada la noción de Lisp (1958).
1968: Algol68 permite redondas soportes (~) como una forma abreviada de empezar ~ finales llamativos bloques de símbolos. Estos se llaman símbolos breves , cf wp: Algol68 Símbolos en negrita , esto permite que los bloques de código se traten como expresiones . A68 también tiene abreviaturas breves como C's ?: Operador ternario, por ejemplo, en x:=(c|s1|s2)lugar de C's x=c?s1|s2. Del mismo modo, esto se aplica a las declaraciones if y case . ¢ BTW: A68 es de donde el shell obtuvo su esac & fi ¢
NevilleDNZ
24

Los corchetes []son más fáciles de escribir, desde el terminal IBM 2741 que fue "ampliamente utilizado en el sistema operativo Multics" , que a su vez tenía a Dennis Ritchie, uno de los creadores del lenguaje C como miembro del equipo de desarrollo .

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

¡Tenga en cuenta la ausencia de llaves en el diseño de IBM 2741!

En C, los "corchetes" se "toman", ya que se usan para matrices y punteros . Si los diseñadores de lenguaje esperaran que las matrices y los punteros fueran más importantes / utilizados con más frecuencia que los bloques de código (lo que parece una suposición razonable a su lado, más sobre el contexto histórico del estilo de codificación a continuación), eso significaría que las llaves se irían a "menos importantes "sintaxis.

La importancia de las matrices es bastante evidente en el artículo El desarrollo del lenguaje C de Ritchie. Incluso hay una suposición explícitamente establecida de "prevalencia de punteros en los programas en C" .

... el nuevo lenguaje retuvo una explicación coherente y viable (si es inusual) de la semántica de las matrices ... Dos ideas son las más características de C entre los lenguajes de su clase: la relación entre matrices y punteros ... La otra característica de C, su tratamiento de matrices ... tiene virtudes reales . Aunque la relación entre punteros y matrices es inusual, se puede aprender. Además, el lenguaje muestra un poder considerable para describir conceptos importantes, por ejemplo, vectores cuya longitud varía en el tiempo de ejecución, con solo unas pocas reglas y convenciones básicas ...


Para una mejor comprensión del contexto histórico y el estilo de codificación de la época en que se creó el lenguaje C, uno debe tener en cuenta que "el origen de C está estrechamente relacionado con el desarrollo de Unix" y, específicamente, que portar el sistema operativo a un PDP- 11 "condujo al desarrollo de una versión temprana de C" ( fuente de citas ). Según Wikipedia , "en 1972, Unix fue reescrito en el lenguaje de programación C" .

El código fuente de varias versiones antiguas de Unix está disponible en línea, por ejemplo, en el sitio The Unix Tree . De varias versiones presentadas allí, la más relevante parece ser la Segunda Edición de Unix con fecha de 1972-06:

La segunda edición de Unix fue desarrollada para el PDP-11 en Bell Labs por Ken Thompson, Dennis Ritchie y otros. Extendió la Primera Edición con más llamadas al sistema y más comandos. Esta edición también vio el comienzo del lenguaje C, que se utilizó para escribir algunos de los comandos ...

Puede explorar y estudiar el código fuente C desde la página Unix (V2) de la Segunda Edición para tener una idea del estilo de codificación típico de la época.

Un ejemplo destacado que respalda la idea de que en aquel entonces era bastante importante para el programador poder escribir corchetes con facilidad se puede encontrar en el código fuente 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;

Es interesante observar cómo la motivación pragmática de elegir personajes para denotar elementos de sintaxis del lenguaje en función de su uso en aplicaciones prácticas específicas se asemeja a la Ley de Zipf como se explica en esta excelente respuesta ...

La relación observada entre frecuencia y longitud se llama Ley de Zipf

... con la única diferencia de que la longitud en la declaración anterior se sustituye por / generalizada como velocidad de escritura.

mosquito
fuente
55
¿Algo que respalde esta expectativa "aparente" de los diseñadores de idiomas? No se necesita mucha programación en C para notar que las llaves son mucho más comunes que las declaraciones de matriz. Esto realmente no ha cambiado mucho desde los viejos tiempos: eche un vistazo a K&R.
1
Dudo de alguna manera esta explicación. No sabemos lo que se esperaba y podrían haberlo elegido fácilmente al revés, ya que también fueron las personas que decidieron sobre la notación de matriz. Ni siquiera sabemos si pensaban que las llaves eran la opción "menos importante", tal vez les gustaban más las llaves.
thorsten müller
3
@gnat: Los corchetes son más fáciles de escribir en los teclados modernos, ¿esto se aplica a los teclados que existían cuando se implementaron por primera vez Unix y C? No tengo ninguna razón para sospechar que estaban usando el mismo teclado, o que asumirían que otros teclados serían como sus teclados, o que habrían pensado que la velocidad de escritura valdría la pena optimizar por un carácter.
Michael Shaw
1
Además, la ley de Zipf es una generalización de lo que termina sucediendo en los idiomas naturales. C fue construido artificialmente, por lo que no hay razón para pensar que se aplicaría aquí a menos que los diseñadores de C deliberadamente decidieran aplicarlo deliberadamente. Si se aplicara, no hay razón para suponer que simplificaría algo tan corto como un solo personaje.
Michael Shaw
1
@gnat FWIW, grep -Fome dice los *.carchivos del código fuente de CPython (rev. 4b42d7f288c5 porque eso es lo que tengo a mano), que incluye libffi, contiene 39511 {(39508 {, no sé por qué dos llaves no están cerradas), pero solo 13718 [(13702 [) Eso es contar las ocurrencias en cadenas y en contextos no relacionados con esta pregunta, por lo que esto no es realmente exacto, incluso si ignoramos que la base del código puede no ser representativa (tenga en cuenta que este sesgo podría ir en cualquier dirección). Aún así, un factor de 2.8?
1

C (y posteriormente C ++ y C #) heredó su estilo de refuerzo de su predecesor B , que fue escrito por Ken Thompson (con contribuciones de Dennis Ritchie) en 1969.

Este ejemplo es de la referencia de usuarios a B de Ken Thompson (a través de 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 se basó nuevamente en BCPL , un lenguaje escrito por Martin Richards en 1966 para el sistema operativo Multics. El sistema de arriostramiento de B utilizaba solo llaves redondas, modificadas por caracteres adicionales (Ejemplo de factoriales de impresión de Martin Richards, a través de 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)

Las llaves usadas en B y en los lenguajes posteriores "{...}" es una mejora que Ken Thompson hizo sobre el estilo de llave compuesto original en BCPL "$ (...) $".

ProfetaV
fuente
1
No. Parece que Bob Bemer ( en.wikipedia.org/wiki/Bob_Bemer ) es responsable de esto - "... usted propuso que podrían usarse en lugar de las palabras clave Algol 'begin' y 'end', que es exactamente cómo se usaron luego en el lenguaje C. " (de bobbemer.com/BRACES.HTM )
SChepurin
1
El $( ... $)formato es equivalente a { ... }en el lexer en BCPL, al igual ??< ... ??>que { ... }en C. La mejora entre los dos estilos está en el hardware del teclado, no en el idioma.