Por que os idiomas exigem parênteses em torno das expressões quando usados ​​com "if" e "while"?

67

Linguagens como C, Java e C ++ requerem parêntese em torno de uma expressão inteira quando usado em um if, whileou switch.

if (true) {
    // Do something
}

em oposição a

if true {
    // Do something
}

Isso me parece estranho, porque os parênteses são redundantes. Neste exemplo, trueé uma expressão única por si só. Os parênteses não transformam seu significado de nenhuma maneira que eu saiba. Por que essa sintaxe ímpar existe e por que é tão comum? Existe um benefício que eu não conheço?

Velovix
fonte
20
Pascal não requer parênteses (porque requer a THEN).
JimmyB
30
Python, Ruby não.
SMCI
31
Acredito que C use parênteses porque os chavetas são opcionais para um corpo de instrução única. Ou talvez a melhor maneira de dizer é que os aparelhos não fazem parte da ifdeclaração, eles apenas criam uma declaração composta.
Fred Larson
7
Ir, curiosamente, requer chaves, mas não parênteses.
Kos
25
A questão é um pouco tautológica. Por que as tampas de bueiro redondas são todas redondas? Por que todos os irmãos são homens? Por que todos os idiomas que exigem parênteses exigem parênteses? As tampas redondas são redondas por definição; irmãos são homens por definição; idiomas que requerem parênteses requerem parênteses por definição.
precisa

Respostas:

155

É preciso haver uma maneira de dizer onde a condição termina e o ramo começa. Existem muitas maneiras diferentes de fazer isso.

Em algumas línguas, não há condicionais em tudo , por exemplo, em Smalltalk, Auto, Novilíngua, Io, Ioke, Seph e Fancy. A ramificação condicional é simplesmente implementada como um método normal, como qualquer outro método. O método é implementado em objetos booleanos e é chamado em um booleano. Dessa forma, a condição é simplesmente o receptor do método, e os dois ramos são dois argumentos, por exemplo, em Smalltalk:

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

Caso você esteja mais familiarizado com Java, isso é equivalente ao seguinte:

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

Na família de linguagens Lisp, a situação é semelhante: condicionais são apenas funções normais (na verdade, macros) e o primeiro argumento é a condição, o segundo e o terceiro argumentos são os ramos, portanto, são apenas argumentos de funções normais, e há nada de especial é necessário para delimitá-los:

(if aBooleanExpression 23 42)

Alguns idiomas usam palavras-chave como delimitadores, por exemplo, Algol, Ada, BASIC, Pascal, Modula-2, Oberon, Oberon-2, Oberon ativo, Componente Pascal, Zonnon, Modula-3:

IF aBooleanExpression THEN RETURN 23 ELSE RETURN 42;

No Ruby, você pode usar uma palavra-chave ou um separador de expressões (ponto-e-vírgula ou nova linha):

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

O Go requer que as ramificações sejam blocos e não permite expressões ou declarações, o que torna obrigatórias as chaves. Portanto, parênteses não são necessários, embora você possa adicioná-los, se quiser; Perl6 e Rust são semelhantes a este respeito:

if aBooleanExpression { return 23 } else { return 42 }

Algumas linguagens usam outros caracteres não alfanuméricos para delimitar a condição, por exemplo, Python:

if aBooleanExpression: return 23
else: return 42

A linha inferior é: você precisa de alguma maneira de dizer onde a condição termina e o ramo começa. Existem muitas maneiras de fazê-lo, os parênteses são apenas uma delas.

Jörg W Mittag
fonte
8
Visão geral muito boa.
Peter - Reintegrar Monica
2
é claro que em um idioma em que expressões simples não são uma declaração (por exemplo, algo como o BASIC antigo, em que um valor calculado deve ser atribuído a uma variável ou passado para outra declaração) ou onde não há operadores de prefixo, você sempre deve poder para identificar o final de uma expressão e o início de uma instrução de qualquer maneira. Definitivamente, pude ver uma variante BASIC gerenciando sem um delimitador no final de uma instrução IF.
Periata Breatta 7/11
4
Além disso, como o C foi projetado nos anos 70 e o cálculo era caro, adicionar um pequeno parêntese provavelmente tornaria o analisador um pouco mais fácil de escrever.
Machado
4
Re: Lisp: "na verdade, macros". Na prática, o IF é um formulário especial no esquema e no CL (apenas para ser completo).
Coredump
11
@Leushenko: E, por exemplo, no MISC, que é preguiçoso por padrão, todas as formas condicionais são apenas funções normais, nem macros nem formas especiais. (Na verdade, AFAIR, MISC tem zero de formas especiais?)
Jörg W Mittag
70

Os parênteses são desnecessários apenas se você usar chaves.

if true ++ x;

Por exemplo, torna-se ambíguo sem eles.

Telastyn
fonte
28
@RobertHarvey - Os parênteses são exigidos por praticamente todos os idiomas que eu conheço. Certamente C e seus parentes. O OP está perguntando por que eles são necessários - e é porque o idioma se tornaria ambíguo caso contrário.
Telastyn #
25
No topo da minha cabeça, os parênteses não são necessários para if: básico, montagem, python, bash / zsh, tcl, lote, foda-cérebro ou código de máquina. A falta de parênteses torna- ifse ambígua se o idioma foi projetado para depender deles.
Candied_orange 7/11
12
Estou surpreso que ninguém tenha mencionado a versão mais lógica e legível - em Pascal (inclusive Delphi) é if Condition then ....
Ulrich Gerhardt
18
Go é um bom exemplo de fazer o oposto. Torna o aparelho {}obrigatório e, portanto, não requer parênteses em torno da expressão. Não só é parens não é obrigatório, mas se eu me lembro acrescentando corretamente parens causaria um erro de compilação - eles estão proibidos
slebetman
10
@Eiko Deixe-me reformular. O exemplo na resposta é sintaticamente ambíguo, mesmo que seja semanticamente inequívoco (como você observou). Porém, como a fase de análise ocorre antes da análise semântica, o analisador encontrará a ambiguidade - e deve adivinhar informações incorretas ou falhar. Se (por qualquer motivo) o analisador optar por não falhar, o analisador semântico funcionará com a árvore resultante, seja ela qual for. Eu não vi um compilador em que o analisador semântico está disposto a pedir ao analisador para analisar novamente a subárvore e fazer uma escolha diferente na construção sintaticamente ambígua.
Theodoros Chatzigiannakis
21

Parênteses em uma ifinstrução não têm o mesmo significado que os parênteses usados ​​em uma expressão aritmética. Parênteses em uma expressão aritmética são usados ​​para agrupar expressões. Parênteses em uma ifinstrução são usados ​​para delimitar a expressão booleana; isto é, para diferenciar a expressão booleana do restante da ifinstrução.

Em uma ifinstrução, parênteses não executam uma função de agrupamento (embora, dentro da ifinstrução, você ainda possa usar parênteses para agrupar expressões aritméticas. O conjunto externo de parênteses serve para delimitar toda a expressão booleana). Torná-los necessários simplifica o compilador, já que o compilador pode confiar nesses parênteses sempre presentes.

Robert Harvey
fonte
Não vejo como isso simplifica o compilador. A regra `IF '(' expression ')' statement` não é mais simples que IF primary_expression statement. Observe que o último é igualmente inequívoco.
precisa saber é o seguinte
@ user58697: Não, apenas este último tem a ambiguidade em que um operador postfix primary_expressionnão pode ser distinguido de um operador prefixo em uma instrução de expressão. Para copiar a resposta de Telastyn if true ++ x;,. Além disso, se existirem instruções vazias, if a & f;pode ser uma instrução vazia e binária &dentro da condição ou unária &no início da instrução. Mas, ao combinar parênteses, há exatamente uma correspondência para a abertura (
MSalters 9/11/16
@MSalters Um operador postfix não analisa como primário. Uma expressão primário é uma das IDENTIFIER, CONSTANT, STRING_LITERALe '(' expression ')'.
precisa saber é o seguinte
@ user58697: Você parece ter um idioma específico em mente. E parece haver uma regra de que parênteses não são necessários se e somente se as condições forem "IDENTIFIER, CONSTANT ou STRING_LITERAL". Não sei se isso facilita as coisas.
MSalters 9/11/16
16

Como outros já apontaram parcialmente, isso se deve ao fato de que expressões também são instruções válidas e, no caso de um bloco com apenas uma instrução, você pode descartar chaves. Isso significa que o seguinte é ambíguo:

if true
    +x;

Porque pode ser interpretado como:

if (true + x) {}

ao invés de:

if (true) {+x;}

Um número de idiomas (por exemplo, Python) permite evitar o parêntese, mas ainda possui um marcador de condição final:

se Verdadeiro : + x

No entanto, você está certo de que poderíamos definir um idioma em que os parênteses nunca sejam necessários: um idioma em que uma expressão não é uma declaração válida não terá esse problema.

Infelizmente, isso significa que coisas como:

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

seria não ser instruções válidas, então você teria que introduzir alguma sintaxe estranha para ser capaz de realizar tais ações sem criar expressões. Uma maneira simples de fazer isso é simplesmente acrescentar a expressão por um marcador como [statement]:

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

Agora a ambiguidade desaparece, pois você teria que escrever:

if true
    [statement] ++x;

Mas como você pode ver, não vejo essa linguagem difundida, pois colocar os parênteses em torno de uma ifcondição-(ou a :no final) é muito melhor do que colocar um marcador para cada expressão.


Nota : o uso de um [statement]marcador é apenas a sintaxe mais simples que eu poderia pensar. No entanto, você pode ter duas sintaxes completamente distintas para expressões e declarações sem ambiguidade entre elas, que não exigiriam esse marcador. O problema é: a linguagem seria extremamente estranha, pois, para fazer as mesmas coisas em uma expressão ou declaração, você teria que usar uma sintaxe completamente diferente.

Uma coisa que vem à mente para ter duas sintaxes separados sem um marcador tão explícito seria, por exemplo: declarações de força para usar símbolos unicode (então ao invés de forvocê usar alguma variação unicode das letras f, oe r), enquanto expressões para ser Somente ASCII.

Bakuriu
fonte
2
Existe realmente uma linguagem que usa esse marcador de expressão: Se você deseja avaliar uma expressão por seus efeitos colaterais, precisa explicitamente discardseu valor em Nim . No entanto, isso é feito apenas para segurança do tipo, não por razões sintáticas.
amon
@ amon Nice, eu não sabia disso. De qualquer forma, como eu disse, o marcador não é realmente necessário, é apenas uma maneira simples de alcançar essa distinção sem inventar sintaxes não intuitivas.
21316 Bakuriu
11
@ amon - muitas variantes do BASIC também têm uma separação estrita entre expressões e declarações. Expressões são permitidas apenas em locais onde o valor será realmente usado (por exemplo, atribuições de variáveis, instruções como PRINT que executam uma ação e assim por diante). Os procedimentos que não são usados ​​para calcular um valor são invocados por uma palavra-chave (normalmente "CHAMADA", embora pelo menos uma variante que conheço use "PROC") que prefixa seu nome. E assim por diante. O BASIC geralmente delimita o final da expressão em uma instrução IF com "THEN", mas não vejo razão técnica para que esse requisito não possa ser descartado.
você precisa saber é o seguinte
11
Existe uma linguagem muito popular nos anos 80 que possui blocos sem parênteses, chaves, dois pontos ou outro marcador e aceita expressões como instruções em todos os lugares e algumas instruções agem como expressões (operadores compostos como + = e ++). É pior, existe um pré-processador estúpido antes do compilador (o ?simbol, por exemplo, é uma função após o PP). Não existe ;. Claro, ele precisa de um marcador para a linha de continuação, mas isso é desencorajado. harbour.github.io/doc/clc53.html#if-cmd . O compilador é rápido e simples (criado com o Bison / Flex).
Maniero 25/02
@bigown Eles conseguir isso usando uma sintaxe separada para lógicas-condições, por isso, basicamente, as condições para if, whileECC são limitados em comparação com expressões genéricas usadas em outros idiomas. Claro: se você tiver mais de duas categorias sintáticas (como declaração, expressão, expressão lógica, expressão de café, ...), poderá trocar alguma liberdade.
Bakuriu 25/02
10

É comum que os idiomas da família C exijam esses parênteses, mas não universais.

Uma das mudanças sintáticas mais visíveis do Perl 6 é que eles modificaram a gramática de forma que você não tem que dar os parênteses ao redor if, fore as condições de declarações semelhantes. Então, algo assim é perfeitamente válido no Perl 6:

if $x == 4 {
    ...
}

como é

while $queue.pop {
    ...
}

No entanto, como são apenas expressões, você pode colocar parênteses em volta deles, se desejar. Nesse caso, são apenas agrupamentos comuns, em vez de uma parte necessária da sintaxe, como em C, C #, Java etc.

Rust possui sintaxe semelhante à Perl 6 neste departamento:

if x == 4 {
    ...
}

Parece-me que uma característica das linguagens mais modernas inspiradas em C é olhar para coisas assim e pensar em removê-las.

Matthew Walton
fonte
Embora sua resposta forneça algumas dicas sobre outros idiomas, ela não responde "Por que essa sintaxe estranha existe e por que é tão comum?"
Você está bem correto. Eu realmente queria adicionar contexto à pergunta, o que não é algo fácil de fazer nesta plataforma. Eu acho que, em última análise, se eu fornecer uma resposta para a pergunta, é "apenas porque, como não há razão técnica, a gramática não poderia ter se acomodado em não tê-la". Mas essa não é uma resposta muito útil.
Matthew Walton
No Perl 5, existem ambos. Para construções normais if ou em loop com BLOCOS, os parâmetros são necessários, por exemplo, em if ( $x == 4 ) { ... }ou foreach my $foo ( @bar ) { ... }. Quando a notação postfix é usada, as parênteses são opcionais, como em return unless $foo;ou ++$x while s/foo/bar/g;.
simbabque
6

Há um aspecto que me surpreende que nenhuma das respostas existentes tenha surgido.

C, e muitos derivados e similares C, têm uma peculiaridade em que o valor de uma atribuição é o valor atribuído. Uma conseqüência disso é que uma atribuição pode ser usada onde um valor é esperado.

Isso permite que você escreva coisas como

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

ou

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

ou

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

(que é implicitamente tratado como while (n < m && *p1++ = *p2++ != 0) { n++; }porque C trata diferente de zero como verdadeiro; aliás, acho que isso é apenas strncpy () na biblioteca padrão C)

ou mesmo

if (x = 17);

e tudo é válido. Nem todas as combinações sintaticamente válidas são necessariamente úteis (e os compiladores modernos alertam especificamente sobre atribuições dentro de condicionais, porque é um erro comum), mas algumas delas são realmente úteis.

A análise de tais declarações provavelmente seria muito mais difícil se não houvesse uma maneira inequívoca de determinar onde a expressão condicional começa e termina.

Os parênteses já eram usados ​​para delimitar nomes de funções dos argumentos das funções, então acho que eles pareciam uma escolha natural também para delimitar palavras-chave dos argumentos das palavras-chave.

Claro, sintaxes alternativas podem ser definidas para fazer a mesma coisa. Mas fazer isso aumentaria a complexidade, particularmente no analisador, que precisaria lidar com dois conjuntos diferentes de sintaxe para praticamente a mesma coisa. Quando C estava sendo projetado, o poder da computação (tanto em termos de capacidade de processamento de números, memória de trabalho e capacidade de armazenamento) era extremamente limitado; qualquer coisa que reduzisse a complexidade com pouco ou nenhum custo para facilitar a leitura era quase certamente uma mudança bem-vinda.

Atualmente, o uso de parênteses pode parecer um pouco arcaico, mas não é assim, dado que alguém com alguma familiaridade com o idioma prejudica a legibilidade em comparação com alguma outra sintaxe capaz de expressar as mesmas coisas.

um CVn
fonte
5

O motivo é principalmente história.

No momento em que o primeiro compilador C foi escrito, os computadores tinham ram, cpu e compiladores muito limitados, onde eram escritos “manualmente” com poucas ferramentas para ajudar os escritores do compilador. Portanto , regras complexas eram caras para implementar em um compilador. C ++, C #, Java etc. foram todos projetados para facilitar a aprendizagem dos programadores em C, portanto , não foram feitas alterações "desnecessárias".

Em linguagens 'c', os condicionais ( if, while, etc) não exigem um blockcódigo off explícito , você pode apenas usar uma declaração simples.

if (a == d) doIt()

ou você pode combinar instruções em uma compound statementcolocando-as em{}

Gostamos do compilador para encontrar o erro que cometemos e enviar como uma mensagem de erro que possamos entender.

Ian
fonte
3

Java e C ++ foram desenvolvidos depois que o C se tornou uma linguagem de programação muito popular. Uma consideração no design de cada uma dessas linguagens era que ela atrairia os programadores em C e atrairia esses programadores a usar a nova linguagem. (Eu era um dos programadores em C que eles conquistaram com sucesso.) O C ++ foi projetado para ser (quase) intercambiável com o código C. A fim de apoiar estes objetivos, tanto C ++ e Java, adotado muito da sintaxe do C, incluindo os parênteses em torno das condições de if, whilee switchdeclarações.

Portanto, a razão pela qual todas essas linguagens exigem parênteses em torno das condições dessas declarações é porque C exige, e a questão é realmente exatamente por que C exige esses parênteses.

As origens da linguagem C são descritas neste artigo por Dennis Ritchie, um dos principais autores de seu desenvolvimento (alguns podem até dizer o principal autor de seu desenvolvimento). Como dito naquele artigo, o C foi desenvolvido originalmente no início dos anos 70 como uma linguagem de programação de sistema para computadores com espaço extremamente limitado na memória principal. Era desejável ter um idioma de nível superior ao idioma assembly, mas, considerando os recursos disponíveis para trabalhar, a facilidade de analisar o idioma também era importante. Exigir os parênteses tornaria relativamente fácil identificar o código condicional.

Pode-se inferir também que a capacidade de escrever programas usando menos caracteres foi considerada uma vantagem, e dois parênteses ocupam menos espaço do que a palavra-chave THENusada no FORTRAN e em outros idiomas de alto nível da época; de fato, como os parênteses também poderiam substituir espaços como delimitadores de símbolos, if(a==b)quatro caracteres inteiros eram menores que IF a==b THEN.

De qualquer forma, foi necessário encontrar um equilíbrio entre a facilidade com que os seres humanos seriam capazes de ler, escrever e entender programas escritos em C, a facilidade com que um compilador poderia analisar e compilar programas escritos em C e quantos kilobytes (!) seria necessário para a fonte do programa e o próprio compilador. E parênteses em torno das condições de if, whilee switch declarações foram como as pessoas escolheram encontrar esse equilíbrio no design de C.

Como evidenciado em várias outras respostas, uma vez que você remove as circunstâncias específicas sob as quais C foi desenvolvido, todos os tipos de formas alternativas de sintaxe foram usados ​​para as condições de várias linguagens de programação. Portanto, os parênteses realmente se resumem a uma decisão de design que foi tomada por algumas pessoas sob certas restrições em um determinado momento da história.

David K
fonte
Não tenho certeza se é justo dizer que o C ++ foi projetado da maneira que era "para atrair os programadores a usar uma nova linguagem". Lembre-se de C com aulas ?
um CVn
@ MichaelKjörling É certo que os desenvolvedores de Java foram muito mais explícitos sobre o "cortejar". Mas observe que o artigo vinculado cita, como uma das razões pelas quais Stroustrup escolheu começar com C como base de sua linguagem, que C foi amplamente utilizado. Uma maneira pela qual isso motivou a permanecer perto de C foi porque o código existente poderia ser facilmente adaptado (como eu já afirmei) - mas também os codificadores existentes poderiam se adaptar facilmente.
9306 David K
@ MichaelKjörling Suponho que o texto original da minha resposta sugerisse que o "cortejar" era um fator maior no design da linguagem do que realmente era. Eu editei a resposta para tentar esclarecer que era apenas uma coisa que foi incluída no design da linguagem.
9309 David K
3

Muitos aqui argumentam que, sem os parênteses, a sintaxe seria ambígua e implica silenciosamente que isso seria de alguma forma ruim ou até impossível.

De fato, os idiomas têm muitas maneiras de lidar com ambiguidades. A precedência do operador é apenas uma instância deste tópico.

Não, a ambiguidade não é a razão dos parênteses. Eu acho que alguém poderia simplesmente criar uma versão de C que não exija os parênteses em torno da condição (tornando-os opcionais) e que ainda crie código válido em todos os casos. O exemplo de if a ++ b;poderia ser interpretado como sendo equivalente a if (a) ++b;ou if (a++) b;, o que parecer mais apropriado.

A questão de por que Dennis Ritchie optou por tornar obrigatório o () (e assim cunhar esse meme para muitas línguas derivadas) é bastante linguística. Acho que a noção de afirmar claramente que a condição é uma expressão e não um comando foi o pai do pensamento.

E, de fato, C foi projetado para ser um analisável usando um analisador de uma passagem. O uso de uma sintaxe com parênteses obrigatórios em torno da condição suporta esse aspecto.

Alfe
fonte
Eu vejo um voto negativo na minha resposta. Por favor, seja gentil em explicar em um comentário o que você não gostou. Talvez eu possa melhorar isso então.
Alfe
0

Parênteses em torno das ifcondições não são necessários no Fortran, Cobol, PL / 1, Algol, Algo-68, Pascal, Modula, XPL, PL / M, MPL, ... ou em qualquer outro idioma que possua uma thenpalavra - chave. thenserve para delimitar conditiono seguinte statement.

O parêntese de fechamento em C etc. funciona como thene o de abertura é formalmente redundante.

As observações acima se aplicam a idiomas analisados ​​tradicionalmente.

user207421
fonte
Fortran faz requer parênteses em todas as versões do seu IF, incluindo um estrutural.
Netch 10/11/16