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

93

La definición de "lenguaje de estilo C" se puede simplificar prácticamente hasta "usar 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.)?

¿Hay algún beneficio real para la productividad de los programadores que proviene de estas llaves o si los diseñadores de nuevos lenguajes buscan alternativas (es decir, los tipos detrás de Python)?

Wikipedia nos dice que C utiliza dijo llaves, 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, C-family languages son aquellos que usan la sintaxis de bloque tipo C (incluyendo llaves para comenzar y finalizar el bloque) ...

    
pregunta SomeKittens 26.02.2013 - 15:59

3 respuestas

102

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

  

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

Desde enlace

  

BCPL introdujo e implementó varias innovaciones que se volvieron bastante   Elementos comunes en el diseño de lenguajes posteriores. Así, fue la   primer lenguaje de programación de corchetes (uno usando {} como bloque   delimitadores), y fue el primer idioma que se usó // para marcar en línea   comentarios.

Desde enlace

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 $) fueron lexicográficamente equivalentes a { y } . Los Digraphs y los trigraphs se mantuvieron en C (aunque un conjunto diferente para el reemplazo de llaves). ??< and ??> ).

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

De Referencia de usuarios a B por 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 se usaron llaves en forma de mano corta para begin y end dentro de Algol.

  

Recuerdo que también los incluiste en el código de la tarjeta de 256 caracteres   que publicaste en el CACM, porque me pareció interesante que   propuso que podrían utilizarse en lugar del 'comienzo' de Algol y   Palabras clave 'finales', que es exactamente como se usaron más tarde en la C   idioma.

De enlace

El uso de corchetes (como un 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 los arreglos en Algol y BCPL, y las llaves para bloques en BCPL, había poca necesidad o deseo de cambiar esto cuando se crea otro idioma.

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

Vea 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 utiliza. En los editores basados en vi, colocar el cursor sobre un abrir / cerrar coincidente y presionar % luego moverá el cursor al otro carácter coincidente. Esto es muy eficiente con los lenguajes basados en C en la antigüedad, menos ahora.

Una mejor comparación sería entre {} y begin / end , que eran las opciones del día (el espacio horizontal era precioso). Muchos idiomas de Wirth se basaron en un estilo begin y end (Algol (mencionado anteriormente), pascal (muchos con los que están familiarizados) y la familia Modula).

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

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

  Los programadores de

C y Ratfor encuentran 'begin' y 'end' voluminosos en comparación con {y}.

Lo que trata de todo lo que se puede decir, su familiaridad y preferencia.

    
respondido por el user40980 26.02.2013 - 16:29
24

Las llaves cuadradas [] son más fáciles de escribir, desde la fecha en que IBM 2741 terminal que se encontraba "ampliamente utilizado en Multics" OS, que a su vez tenía a Dennis Ritchie , uno de los creadores del lenguaje C como miembro del equipo dev .

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

En C, los tirantes cuadrados se "toman", ya que se usan para matrices y punteros . Si los diseñadores de idiomas esperaban que los arreglos y los punteros fueran más importantes / se usaran con más frecuencia que los bloques de código (que suena como un supuesto razonable a su lado, más en el contexto histórico del estilo de codificación a continuación), lo que significaría que los tirantes irían a la sintaxis" menos importante ".

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

  

... nuevo idioma retuvo una explicación coherente y viable (si es inusual) de la semántica de las matrices ... Dos ideas son la más característica de C entre los idiomas de su clase: la relación entre matrices y punteros ... El otro rasgo característico de C, su tratamiento de matrices ... tiene virtudes reales . Aunque la relación entre los punteros y las matrices es inusual, puede aprenderse. Además, el lenguaje muestra considerable poder 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 comprender mejor el contexto histórico y el estilo de codificación de la época en que se creó el lenguaje C, hay que tener en cuenta que "el origen de C está estrechamente relacionado con el desarrollo de Unix" y , específicamente, que la conversión del SO a un PDP-11 "condujo al desarrollo de una versión temprana de C" ( fuente de citas ). Según Wikipedia , "en 1972, Unix se reescribió 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 las diversas versiones presentadas allí, la más relevante parece ser Segunda Edición Unix de 1972-06 :

  

La segunda edición de Unix fue desarrollada para el PDP-11 en los Laboratorios Bell 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 usó para escribir algunos de los comandos ...

Puede navegar y estudiar el código fuente de C desde 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 apoya la idea de que en ese entonces era bastante importante para el programador poder escribir corchetes con facilidad se puede encontrar en V2 / c / ncc.c código fuente:

/* 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;

Es interesante observar cómo la motivación pragmática de seleccionar caracteres 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 la frecuencia y la longitud se llama Ley de Zipf

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

    
respondido por el gnat 26.02.2013 - 16:05
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 los 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 usaba solo tirantes redondos, modificados por caracteres adicionales (ejemplo de Imprimir factoriales 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 que se usan en B y los idiomas subsiguientes "{...}" son una mejora que Ken Thompson realizó sobre el estilo original de la abrazadera compuesta en BCPL "$ (...) $".

    
respondido por el ProphetV 26.02.2013 - 16:34

Lea otras preguntas en las etiquetas