¿Por qué los idiomas requieren paréntesis alrededor de las expresiones cuando se usan con "if" y "while"?

67

Lenguajes como C, Java, C ++ y todos requieren paréntesis alrededor de toda una expresión cuando se utiliza en una if, whileo switch.

if (true) {
    // Do something
}

Opuesto a

if true {
    // Do something
}

Esto me parece extraño porque los paréntesis son redundantes. En este ejemplo, truees una sola expresión por sí sola. El paréntesis no transforma su significado de ninguna manera que yo sepa. ¿Por qué existe esta sintaxis extraña y por qué es tan común? ¿Hay algún beneficio que desconozca?

Velovix
fuente
20
Pascal no requiere paréntesis (porque requiere a THEN).
JimmyB
30
Python, Ruby no.
smci
31
Creo que C usa paréntesis porque las llaves son opcionales para un cuerpo de una sola declaración. O tal vez una mejor manera de decirlo es que las llaves no son parte de la ifdeclaración, solo crean una declaración compuesta.
Fred Larson
77
Ir, curiosamente, requiere llaves pero no paréntesis.
Kos
25
La pregunta es un poco tautológica. ¿Por qué las tapas de alcantarilla redondas son redondas? ¿Por qué todos los hermanos son hombres? ¿Por qué todos los idiomas que requieren paren requieren parens? Las tapas de registro redondas son redondas por definición; los hermanos son varones por definición; los idiomas que requieren parens requieren parens por definición.
Eric Lippert el

Respuestas:

155

Tiene que haber alguna forma de saber dónde termina la condición y comienza la rama. Hay muchas formas diferentes de hacerlo.

En algunos idiomas, no hay condicionales en absoluto , por ejemplo, en Smalltalk, Ser, la neolengua, Io, Ioke, Seph y Fancy. La ramificación condicional se implementa simplemente como un método normal como cualquier otro método. El método se implementa en objetos booleanos y se llama en un booleano. De esa manera, la condición es simplemente el receptor del método, y las dos ramas son dos argumentos, por ejemplo, en Smalltalk:

aBooleanExpression ifTrue: [23] ifFalse: [42].

En caso de que esté más familiarizado con Java, esto es equivalente a lo siguiente:

aBooleanExpression.ifThenElse(() -> 23, () -> 42);

En la familia de lenguajes Lisp, la situación es similar: los condicionales son solo funciones normales (en realidad, macros) y el primer argumento es la condición, el segundo y el tercer argumento son las ramas, por lo que son solo argumentos de funciones normales, y hay no se necesita nada especial para delimitarlos:

(if aBooleanExpression 23 42)

Algunos idiomas usan palabras clave como delimitadores, por ejemplo, Algol, Ada, BASIC, Pascal, Modula-2, Oberon, Oberon-2, Oberon activo, Componente Pascal, Zonnon, Modula-3:

IF aBooleanExpression THEN RETURN 23 ELSE RETURN 42;

En Ruby, puede usar una palabra clave o un separador de expresión (punto y coma o nueva línea):

if a_boolean_expression then 23 else 42 end

if a_boolean_expression; 23 else 42 end

# non-idiomatic, the minimum amount of whitespace required syntactically
if a_boolean_expression
23 else 42 end

# idiomatic, although only the first newline is required syntactically
if a_boolean_expression
  23
else
  42
end

Ir requiere que las ramas sean bloques y no permite expresiones o declaraciones, lo que hace que las llaves sean obligatorias. Por lo tanto, no se requieren paréntesis, aunque puede agregarlos si lo desea; Perl6 y Rust son similares a este respecto:

if aBooleanExpression { return 23 } else { return 42 }

Algunos idiomas usan otros caracteres no alfanuméricos para delimitar la condición, por ejemplo, Python:

if aBooleanExpression: return 23
else: return 42

La conclusión es: necesita alguna forma de saber dónde termina la condición y comienza la rama. Hay muchas formas de hacerlo, los paréntesis son solo uno de ellos.

Jörg W Mittag
fuente
8
Muy buena visión general.
Peter - Restablece a Monica el
2
por supuesto, en un lenguaje donde las expresiones desnudas no son una declaración (por ejemplo, algo así como BASIC anterior, donde un valor calculado debe asignarse a una variable o pasar a otra declaración) o donde no hay operadores de prefijo, siempre debe poder para identificar el final de una expresión y el inicio de una declaración de todos modos. Definitivamente, pude ver una variante BÁSICA que se gestiona sin un delimitador al final de una declaración IF.
Periata Breatta
44
Además, dado que C fue diseñado en los años 70 y el cálculo era costoso, agregar un paréntesis probablemente haría que el analizador fuera un poco más fácil de escribir.
Machado
44
Re: Lisp: "en realidad, macros". En la práctica, IF es una forma especial en Scheme y CL (solo para completar).
coredump
1
@Leushenko: Y, por ejemplo, en MISC, que es vago por defecto, todas las formas condicionales son solo funciones normales, ni macros ni formas especiales. (De hecho, AFAIR, MISC tiene cero formas especiales?)
Jörg W Mittag
70

Los paréntesis solo son innecesarios si usa llaves.

if true ++ x;

Por ejemplo, se vuelve ambiguo sin ellos.

Telastyn
fuente
28
@RobertHarvey: los paréntesis son necesarios en casi todos los idiomas que conozco. Ciertamente, C y sus parientes. El OP pregunta por qué se requieren, y es porque el lenguaje se volvería ambiguo de lo contrario.
Telastyn
25
Justo en la parte superior de mi cabeza, no se requieren paréntesis ifen: básico, ensamblaje, python, bash / zsh, tcl, batch, brainfuck o código de máquina. La falta de paréntesis solo hace que sea ifambiguo si el lenguaje ha sido diseñado para depender de ellos.
candied_orange
12
Estoy asombrado de que nadie haya mencionado la versión más lógica y legible, en Pascal (incluido Delphi) es if Condition then ....
Ulrich Gerhardt
18
Ir es un buen ejemplo de hacer lo contrario. Hace que las llaves sean {}obligatorias y, por lo tanto, no requiere parens alrededor de la expresión. No solo no se requieren parens, sino que si recuerdo correctamente, agregarlos causaría un error de compilación, están prohibidos
Slebetman
10
@Eiko Déjame reformular. El ejemplo en la respuesta es sintácticamente ambiguo, a pesar de que es semánticamente inequívoco (como notó). Pero dado que la fase de análisis ocurre antes del análisis semántico, el analizador encontrará la ambigüedad, y debe hacer una suposición no informada o fallar. Si (por alguna razón) el analizador elige no fallar, el analizador semántico funcionará con el árbol resultante, sea lo que sea. No he visto un compilador en el que el analizador semántico esté dispuesto a pedirle al analizador que vuelva a analizar el subárbol y que haga una elección diferente en la construcción sintácticamente ambigua.
Theodoros Chatzigiannakis
21

Los paréntesis en una ifdeclaración no tienen el mismo significado que los paréntesis utilizados dentro de una expresión aritmética. Los paréntesis en una expresión aritmética se usan para agrupar expresiones. Los paréntesis en una ifdeclaración se usan para delimitar la expresión booleana; es decir, diferenciar la expresión booleana del resto de la ifdeclaración.

En una ifdeclaración, los paréntesis no realizan una función de agrupación (aunque, dentro de la ifdeclaración, aún puede usar paréntesis para agrupar expresiones aritméticas. El conjunto externo de paréntesis sirve para delimitar la expresión booleana completa). Hacerlos necesarios simplifica el compilador, ya que el compilador puede confiar en que los paréntesis siempre estén ahí.

Robert Harvey
fuente
No veo cómo simplifica el compilador. La regla 'IF' ('expresión') 'instrucción' no es más simple que IF primary_expression statement. Tenga en cuenta que este último es igualmente inequívoco.
usuario58697
@ user58697: No, solo este último tiene la ambigüedad de que un operador de postfix primary_expressionno se puede distinguir de un operador de prefijo en una declaración de expresión. Para copiar la respuesta de Telastyn, if true ++ x;. Además, si existen declaraciones vacías, if a & f;puede ser una declaración vacía y binaria &dentro de la condición, o una unaria &al comienzo de la declaración. Pero cuando se combinan paréntesis, hay exactamente una coincidencia para la apertura (
MSalters
@MSalters Un operador de postfix no se analiza como primario. Una expresión primaria es uno de IDENTIFIER, CONSTANT, STRING_LITERALy '(' expression ')'.
user58697
@ user58697: Parece que tienes un lenguaje en particular en mente. Y parece tener una regla de que los paréntesis no son necesarios si y solo si las condiciones son un "IDENTIFICADOR, CONSTANTE o STRING_LITERAL". No estoy seguro de que eso facilite las cosas.
MSalters
16

Como otros ya han señalado parcialmente, esto se debe al hecho de que las expresiones también son declaraciones válidas, y en el caso de un bloque con solo una declaración, puede colocar llaves. Esto significa que lo siguiente es ambiguo:

if true
    +x;

Porque podría interpretarse como:

if (true + x) {}

en lugar de:

if (true) {+x;}

Varios idiomas (p. Ej., Python) le permiten evitar el paréntesis pero aún tienen un marcador de condición final:

si es verdadero : + x

Sin embargo, tiene razón en que podríamos definir un lenguaje donde los paréntesis nunca sean necesarios: un lenguaje donde una expresión no sea una declaración válida no tendrá este problema.

Desafortunadamente, esto significa que cosas como:

 ++x;
 functionCall(1,2,3);

que no sea afirmaciones válidas, por lo que habría que introducir una sintaxis extraña para poder llevar a cabo este tipo de acciones sin crear expresiones. Una manera simple de hacer esto es simplemente anteponer la expresión por un marcador como [statement]:

[statement] ++x;
[statement] functionCall(1,2,3);

Ahora la ambigüedad desaparece ya que tendrías que escribir:

if true
    [statement] ++x;

Pero como puede ver, no veo que dicho lenguaje esté muy extendido ya que poner el paréntesis alrededor de una ifcondición (o una :al final) es mucho mejor que poner ese marcador para cada declaración de expresión.


Nota : el uso de un [statement]marcador es la sintaxis más simple que se me ocurre. Sin embargo, podría tener dos sintaxis completamente distintas para expresiones y declaraciones sin ambigüedad entre ellas, lo que no requeriría dicho marcador. El problema es: el lenguaje sería extremadamente extraño ya que para hacer las mismas cosas en una expresión o una declaración, tendría que usar una sintaxis completamente diferente.

Una cosa que viene a la mente para tener dos sintaxis separadas sin un marcador tan explícito sería, por ejemplo: forzar declaraciones para usar símbolos unicode (así que en lugar de forusar una variación unicode de las letras f, oy r), mientras que las expresiones serán Solo ASCII.

Bakuriu
fuente
2
En realidad, existe un lenguaje que utiliza dicho marcador de declaración de expresión: si desea evaluar una expresión para sus efectos secundarios, necesita explícitamente discardsu valor en Nim . Sin embargo, eso solo se hace por seguridad de tipo, no por razones sintácticas.
amon
@amon Nice, no sabía sobre eso. De todos modos, como dije, el marcador no es realmente necesario, es solo una forma simple de lograr esa distinción sin inventar sintaxis no intuitivas.
Bakuriu el
1
@amon: muchas variantes BÁSICAS también tienen una separación estricta entre expresiones y declaraciones. Las expresiones solo se permiten en lugares donde el valor se utilizará realmente (por ejemplo, asignaciones variables, declaraciones como PRINT que realizan una acción, etc.). Los procedimientos que no se utilizan para calcular un valor son invocados por una palabra clave (generalmente "CALL", aunque al menos una variante que conozco usa "PROC") que prefija su nombre. Y así. BASIC generalmente delimita el final de la expresión en una instrucción IF con "ENTONCES", pero no puedo ver ninguna razón técnica por la que no se pueda eliminar el requisito.
Periata Breatta
1
Existe un lenguaje muy popular en los años 80 que tiene bloques sin pares, llaves, dos puntos u otro marcador y acepta expresiones como declaraciones en todas partes y algunas declaraciones actúan como expresiones (operadores compuestos como + = y ++). Es peor, hay un preprocesador tonto antes del compilador ( ?simbol, de hecho, es una función después de PP). No hay ninguna ;. Claro, necesita un marcador para la línea de continuación, pero esto se desaconseja. harbour.github.io/doc/clc53.html#if-cmd . El compilador es rápido y simple (creado con Bison / Flex).
Maniero
@bigown Lo logran mediante el uso de una sintaxis separada para las condiciones lógicas, por lo que, básicamente, las condiciones para if, whileecc son limitadas en comparación con las expresiones genéricas utilizadas en otros idiomas. Claro: si tiene más de dos categorías sintácticas (como enunciado, expresión, expresión lógica, expresión de café, ...) puede intercambiar cierta libertad.
Bakuriu
10

Es común que los lenguajes de la familia C requieran estos paréntesis, pero no son universales.

Uno de los cambios más notables sintácticas de Perl 6 es que la gramática modificada de modo que usted no tiene que dar los paréntesis alrededor if, fory las condiciones de declaraciones similares. Entonces algo como esto es perfectamente válido en Perl 6:

if $x == 4 {
    ...
}

como es

while $queue.pop {
    ...
}

Sin embargo, debido a que son solo expresiones, puede poner paréntesis alrededor de ellas si lo desea, en cuyo caso son solo agrupaciones ordinarias en lugar de una parte obligatoria de la sintaxis como están en C, C #, Java, etc.

Rust tiene una sintaxis similar a Perl 6 en este departamento:

if x == 4 {
    ...
}

Me parece que una característica de los lenguajes más modernos inspirados en C es mirar cosas como esta y preguntarse sobre cómo eliminarlas.

Matthew Walton
fuente
Si bien su respuesta proporciona información sobre otros idiomas, no responde "¿Por qué existe esta sintaxis extraña y por qué es tan común?"
Tienes toda la razón. Realmente estaba buscando agregar contexto a la pregunta, que no es algo fácil de hacer en esta plataforma. Supongo que, en última instancia, si proporciono una respuesta a la pregunta, es "solo porque, dado que no hay una razón técnica, la gramática no podría haber aceptado no tenerlas". Pero esa no es una respuesta muy útil.
Matthew Walton el
En Perl 5 hay ambos. Para construcciones normales if o de bucle con BLOCK, se requieren los parens, por ejemplo, en if ( $x == 4 ) { ... }o foreach my $foo ( @bar ) { ... }. Cuando se utiliza la notación postfix, los parens son opcionales, como en return unless $foo;o ++$x while s/foo/bar/g;.
simbabque
6

Hay un aspecto que me sorprende que ninguna de las respuestas existentes haya mencionado.

C, y muchos derivados y similitudes de C, tienen una peculiaridad en que el valor de una asignación es el valor asignado. Una consecuencia de esto es que se puede usar una asignación donde se espera un valor.

Esto te permite escribir cosas como

if (x = getValue() == 42) { ... }

o

if (x == y = 47) { ... }

o

unsigned int n = 0 /* given m == SOME_VALUE */;
while (n < m && *p1++ = *p2++) { n++; }

(que se trata implícitamente como while (n < m && *p1++ = *p2++ != 0) { n++; }porque C trata a los no distintos de cero como verdaderos; por cierto, creo que se trata de strncpy () en la biblioteca estándar de C)

o incluso

if (x = 17);

Y todo es válido. No todas las combinaciones sintácticamente válidas son necesariamente útiles (y los compiladores modernos advierten específicamente sobre las asignaciones dentro de los condicionales, porque es un error común), pero algunas de ellas son realmente útiles.

Analizar tales declaraciones probablemente sería mucho más difícil si no hubiera una forma inequívoca de determinar dónde comienza y termina la expresión condicional.

Los paréntesis ya se usaban para delimitar los nombres de las funciones de los argumentos de las funciones, por lo que creo que también parecían una opción natural para delimitar las palabras clave de los argumentos de las palabras clave.

Claro, se podrían definir sintaxis alternativas para hacer lo mismo. Pero hacerlo aumentaría la complejidad, particularmente en el analizador que luego necesitaría lidiar con dos conjuntos diferentes de sintaxis para la misma cosa. Cuando se estaba diseñando C, el poder de cómputo (tanto en términos de capacidad de cálculo de números, memoria de trabajo y capacidad de almacenamiento) era extremadamente limitado; Cualquier cosa que redujera la complejidad a un costo bajo o nulo para la legibilidad fue casi con certeza un cambio bienvenido.

El uso de paréntesis puede parecer un poco arcaico hoy en día, pero no es así dado que alguien con cierta familiaridad con el lenguaje, perjudica la legibilidad en comparación con otra sintaxis que es capaz de expresar las mismas cosas.

un CVn
fuente
5

La razón es principalmente historia.

En el momento en que se escribió el primer compilador de C, las computadoras tenían ram, cpu y compiladores muy limitados, escritos "a mano" con pocas herramientas para ayudar a los escritores de compiladores. Por lo tanto , las reglas complejas eran costosas de implementar en un compilador. C ++, C #, Java, etc., fueron diseñados para que los programadores de C puedan aprender fácilmente, por lo tanto , no se realizaron cambios "innecesarios".

En los lenguajes 'c like' los condicionales ( if, while, etc) no requieren un blockcódigo explícito , solo puede usar una declaración simple.

if (a == d) doIt()

o puede combinar declaraciones en una compound statementponiéndolas en{}

Nos gusta que el compilador encuentre el error que cometemos y que nos dé un mensaje de error que podamos entender.

Ian
fuente
3

Java y C ++ fueron desarrollados después de que C se había convertido en un lenguaje de programación muy popular. Una consideración en el diseño de cada uno de esos lenguajes fue que sería atractivo para los programadores de C y cortejaría a esos programadores para que usaran el nuevo lenguaje. (Fui uno de los programadores de C que cortejaron con éxito). C ++ además fue diseñado para ser (casi) intercambiable con el código de C. Para apoyar estos objetivos, tanto en C ++ y Java adoptaron gran parte de la sintaxis de C, incluyendo los paréntesis alrededor de las condiciones de if, whiley switchdeclaraciones.

Por lo tanto, la razón por la cual todos estos lenguajes requieren paréntesis en torno a las condiciones de esas declaraciones es porque C sí, y la pregunta es realmente por qué C requiere esos paréntesis.

Los orígenes del lenguaje C se describen en este artículo por Dennis Ritchie, uno de los principales autores de su desarrollo (algunos incluso podrían decir el autor principal de su desarrollo). Como se dijo en ese artículo, C se desarrolló originalmente a principios de la década de 1970 como un lenguaje de programación de sistemas para computadoras con espacio extremadamente limitado en la memoria principal. Se deseaba tener un idioma que fuera de nivel superior al lenguaje ensamblador, pero dados los recursos disponibles para trabajar, la facilidad de analizar el idioma también era importante. Requerir los paréntesis facilitaría la identificación del código condicional.

También se podría inferir que la capacidad de escribir programas con menos caracteres se consideraba una ventaja, y dos paréntesis ocupan menos espacio que la palabra clave THENque se usaba en FORTRAN y otros lenguajes de alto nivel en ese momento; de hecho, dado que los paréntesis también podían reemplazar espacios como delimitadores de símbolos, if(a==b)era cuatro caracteres enteros más cortos que IF a==b THEN.

En cualquier caso, hubo que encontrar un equilibrio entre la facilidad con que los seres humanos podrían leer, escribir y comprender los programas escritos en C, la facilidad con que un compilador podría analizar y compilar programas escritos en C, y cuántos kilobytes (!) sería necesario tanto para la fuente del programa como para el compilador en sí. Y los paréntesis en torno a las condiciones de if, whiley las switch declaraciones fue cómo la gente eligió lograr ese equilibrio en el diseño de C.

Como se evidencia en varias otras respuestas, una vez que elimina las circunstancias particulares en las que se desarrolló C, se han utilizado todo tipo de sintaxis alternativas para los condicionales de varios lenguajes de programación. Entonces, los paréntesis realmente se reducen a una decisión de diseño que fue tomada por algunas personas bajo ciertas restricciones en un momento determinado de la historia.

David K
fuente
No estoy seguro de que sea justo decir que C ++ se diseñó de la manera en que fue "para atraer a esos programadores a usar un nuevo lenguaje". ¿Recuerdas C con clases ?
un CVn el
@ MichaelKjörling Es cierto que los desarrolladores de Java fueron mucho más explícitos sobre el "cortejo". Pero tenga en cuenta que el artículo vinculado cita, como una de las razones por las que Stroustrup eligió comenzar con C como base de su lenguaje, que C fue ampliamente utilizado. Una forma en que esto proporcionó una motivación para permanecer cerca de C fue porque el código existente podría adaptarse fácilmente (como ya dije), pero también los codificadores existentes podrían adaptarse fácilmente.
David K
@ MichaelKjörling Supongo que la redacción original de mi respuesta sugirió que el "cortejo" fue un factor más importante en el diseño del lenguaje de lo que realmente fue. He editado la respuesta para tratar de aclarar que era solo una cosa que tenía en cuenta el diseño del lenguaje.
David K
3

Muchos aquí razonan que sin los paréntesis la sintaxis sería ambigua e implican en silencio que esto sería de alguna manera malo o incluso una situación imposible.

De hecho, los idiomas tienen muchas formas de lidiar con las ambigüedades. La precedencia del operador es solo una instancia de este tema.

No, la ambigüedad no es la razón de los paréntesis. Supongo que uno podría simplemente crear una versión de C que no requiera los paréntesis alrededor de la condición (haciéndolos opcionales) y que todavía cree un código válido en todos los casos. El ejemplo de if a ++ b;podría interpretarse como equivalente a if (a) ++b;, o if (a++) b;, lo que le parece más apropiado.

La pregunta de por qué Dennis Ritchie eligió hacer que el () fuera obligatorio (y, por lo tanto, acuñar este meme para muchos idiomas derivados) es bastante lingüístico. Supongo que la noción de afirmar claramente que la condición es una expresión más que una orden fue el padre del pensamiento.

Y, de hecho, C fue diseñado para ser analizable mediante un analizador de una pasada. El uso de una sintaxis con paréntesis obligatorios alrededor de la condición respalda este aspecto.

Alfe
fuente
Veo un voto negativo en mi respuesta. Sea tan amable de explicar en un comentario lo que no le gustó al respecto. Quizás pueda mejorarlo entonces.
Alfe
0

ifNo se requieren paréntesis en torno a las condiciones en Fortran, Cobol, PL / 1, Algol, Algo-68, Pascal, Modula, XPL, PL / M, MPL, ... o en cualquier otro idioma que tenga una thenpalabra clave. thensirve para delimitar conditionlo siguiente statement.

El paréntesis de cierre en C etc. funciona como then, y el de apertura es formalmente redundante.

Las observaciones anteriores se aplican a los idiomas analizados tradicionalmente.

usuario207421
fuente
Fortran hace requerir paréntesis en todas las versiones de su FI, entre ellos uno estructural.
Netch