Avaliação de curto-circuito - Short-circuit evaluation
Estratégias de avaliação |
---|
Avaliação de curto-circuito , avaliação mínima ou avaliação de McCarthy (após John McCarthy ) é a semântica de alguns operadores booleanos em algumas linguagens de programação em que o segundo argumento é executado ou avaliado apenas se o primeiro argumento não for suficiente para determinar o valor do expressão: quando o primeiro argumento da AND
função é avaliado como false
, o valor geral deve ser false
; e quando o primeiro argumento da OR
função for avaliado como true
, o valor geral deve ser true
.
Em linguagens de programação com avaliação preguiçosa ( Lisp , Perl , Haskell ), os operadores booleanos usuais são de curto-circuito. Em outros ( Ada , Java , Delphi ), ambos os operadores booleanos padrão e de curto-circuito estão disponíveis. Para algumas operações booleanas, como exclusivas ou (XOR), não é possível entrar em curto-circuito, pois os dois operandos são sempre necessários para determinar o resultado.
Os operadores de curto-circuito são, com efeito, estruturas de controle em vez de simples operadores aritméticos, pois não são estritos . Em termos de linguagem imperativos (notadamente C e C ++ ), onde os efeitos colaterais são importantes, os operadores de curto-circuito introduzem um ponto de sequência - eles avaliam completamente o primeiro argumento, incluindo quaisquer efeitos colaterais , antes (opcionalmente) de processar o segundo argumento. O ALGOL 68 usou procedimentos para obter operadores e procedimentos de curto-circuito definidos pelo usuário .
O uso de operadores de curto-circuito foi criticado como problemático:
Os conectivos condicionais - " cand " e " cor " para abreviar - são ... menos inocentes do que podem parecer à primeira vista. Por exemplo, cor não distribui sobre cand : compare
- (A cand B) cor C com (A cor C) cand (B cor C);
no caso ¬A ∧ C, a segunda expressão requer que B seja definido, a primeira não. Como os conectivos condicionais complicam o raciocínio formal sobre os programas, é melhor evitá-los.
Definição
Em qualquer linguagem de programação que implemente avaliação de curto-circuito, a expressão é equivalente à expressão condicional e a expressão é equivalente a . Em ambos os casos, x é avaliado apenas uma vez.
x and y
if x then y else x
x or y
if x then x else y
A definição generalizada acima acomoda linguagens vagamente tipadas que têm mais do que os dois valores de verdade True
e False
, onde os operadores de curto-circuito podem retornar a última subexpressão avaliada. Isso é chamado de "último valor" na tabela abaixo. Para uma linguagem estritamente tipada, a expressão é simplificada para e, respectivamente, para o caso booleano.
if x then y else false
if x then true else y
Precedência
Embora AND
toma precedência sobre OR
em muitas línguas, isso não é uma propriedade universal de avaliação de curto-circuito. Um exemplo dos dois operadores tendo a mesma precedência e sendo associativo à esquerda um com o outro é a sintaxe da lista de comandos do shell POSIX .
O seguinte avaliador simples da esquerda para a direita impõe uma precedência de AND
sobre OR
por um continue
:
function short-circuit-eval (operators, values) let result := True for each (op, val) in (operators, values): if op = "AND" && result = False continue else if op = "OR" && result = True return result else result := val return result
Formalização
A lógica de curto-circuito, com ou sem efeitos colaterais, foi formalizada com base na condicional de Hoare . Um resultado é que os operadores sem curto-circuito podem ser definidos fora da lógica de curto-circuito para ter a mesma sequência de avaliação.
Suporte em linguagens de programação e script comuns
Língua | Operadores ávidos | Operadores de curto-circuito | Tipo de resultado |
---|---|---|---|
Advanced Business Application Programming ( ABAP ) | Nenhum |
and , or
|
Booleano 1 |
Ada |
and , or
|
and then , or else
|
boleano |
ALGOL 68 | e, &, ∧; ou, ∨ | andf, orf (ambos definidos pelo usuário) | boleano |
APL |
∧ , ∨ , ⍲ (NAND), ⍱ (nem), etc.
|
:AndIf , :OrIf
|
Booleano 1 |
awk | Nenhum |
&& , ||
|
boleano |
Bash | Nenhum |
&& , ||
|
boleano |
C , Objective-C | Nenhum |
&& , || ,?
|
int ( && , || ), dependente de opnd ( ? )
|
C ++ 2 | Nenhum |
&& , || ,?
|
Booleano ( && , || ), dependente de opnd ( ? )
|
C # |
& , |
|
&& , || , ? ,??
|
Booleano ( && , || ), dependente de opnd ( ? , ?? )
|
ColdFusion Markup Language (CFML) | Nenhum |
AND , OR , && ,||
|
boleano |
D 3 |
& , |
|
&& , || ,?
|
Booleano ( && , || ), dependente de opnd ( ? )
|
Eiffel |
and , or
|
and then , or else
|
boleano |
Erlang |
and , or
|
andalso , orelse
|
boleano |
Fortran 4 |
.and. , .or.
|
.and. , .or.
|
boleano |
Go , Haskell , OCaml | Nenhum |
&& , ||
|
boleano |
Java , MATLAB , R , Swift |
& , |
|
&& , ||
|
boleano |
JavaScript , Julia |
& , |
|
&& , ||
|
Último valor |
Laço | Nenhum |
and , or , && ,||
|
Último valor |
Kotlin |
and , or
|
&& , ||
|
boleano |
Lisp , Lua , Scheme | Nenhum |
and , or
|
Último valor |
MUMPS (M) |
& , !
|
Nenhum | Numérico |
Modula-2 | Nenhum |
AND , OR
|
boleano |
Oberon | Nenhum |
& , OR
|
boleano |
OCaml | Nenhum |
&& , ||
|
boleano |
Pascal |
and , or 5 , 9
|
and_then , or_else 6 , 9
|
boleano |
Perl |
& , |
|
&& , and , || ,or
|
Último valor |
Rubi |
and , or
|
&& , ||
|
Último valor |
PHP |
& , |
|
&& , and , || ,or
|
boleano |
Shell POSIX (lista de comandos) | Nenhum |
&& , ||
|
Último valor (saída) |
Pitão | Nenhum |
and , or
|
Último valor |
Ferrugem |
& , |
|
&& , ||
|
boleano |
Conversa fiada |
& , |
|
and: , or: 7
|
boleano |
ML padrão | Desconhecido |
andalso , orelse
|
boleano |
TTCN-3 | Nenhum |
and , or
|
boleano |
Beckhoff TwinCAT® ( IEC 61131-3 ) 10 |
AND , OR
|
AND_THEN , OR_ELSE
|
boleano |
Visual Basic .NET |
And , Or
|
AndAlso , OrElse
|
boleano |
Visual Basic , Visual Basic for Applications (VBA) |
And , Or
|
Select Case 8
|
Numérico |
Wolfram Language |
And @@ {...} , Or @@ {...}
|
And , Or , && ,||
|
boleano |
ZTT |
& , |
|
Nenhum | boleano |
1 ABAP e APL não têm tipo booleano distinto.
2 Quando sobrecarregado , os operadores &&
e ||
estão ansiosos e pode retornar qualquer tipo.
3 Isso se aplica apenas a expressões avaliadas pelo tempo de execução static if
e static assert
. Expressões em inicializadores estáticos ou constantes de manifesto usam avaliação rápida.
4 Os operadores Fortran não são curtos-circuitos nem ansiosos: a especificação da linguagem permite que o compilador selecione o método de otimização.
5 ISO / IEC 10206: 1990 Extended Pascal permite, mas não exige, curto-circuito.
6 ISO / IEC 10206: 1990 Suporta Pascal estendido and_then
e or_else
.
7 Smalltalk usa semântica de curto-circuito contanto que o argumento para and:
seja um bloco (por exemplo, false and: [Transcript show: 'Wont see me']
).
8 linguagens BASIC que suportavam instruções CASE faziam isso usando o sistema de avaliação condicional, em vez de tabelas de salto limitadas a rótulos fixos.
9 Delphi e Free Pascal são padronizados para avaliação de curto-circuito. Isso pode ser alterado pelas opções do compilador, mas não parece ser usado amplamente.
10 A norma IEC 61131-3 não define realmente se AND
e OR
usa avaliação de curto-circuito e não define os operadores AND_THEN
e OR_ELSE
. As entradas na tabela mostram como funciona para Beckhoff TwinCAT®.
Uso comum
Evitando efeitos colaterais indesejados do segundo argumento
Exemplo usual, usando uma linguagem baseada em C :
int denom = 0;
if (denom != 0 && num / denom)
{
... // ensures that calculating num/denom never results in divide-by-zero error
}
Considere o seguinte exemplo:
int a = 0;
if (a != 0 && myfunc(b))
{
do_something();
}
Neste exemplo, a avaliação de curto-circuito garante que myfunc(b)
nunca seja chamada. Isso ocorre porque é a != 0
avaliada como falsa . Este recurso permite duas construções de programação úteis.
- Se a primeira subexpressão verificar se um cálculo caro é necessário e a verificação for avaliada como falsa , pode-se eliminar o cálculo caro no segundo argumento.
- Ele permite uma construção em que a primeira expressão garante uma condição sem a qual a segunda expressão pode causar um erro em tempo de execução .
Ambos são ilustrados no seguinte snippet C, em que a avaliação mínima evita a desreferência do ponteiro nulo e as buscas de memória em excesso:
bool is_first_char_valid_alpha_unsafe(const char *p)
{
return isalpha(p[0]); // SEGFAULT highly possible with p == NULL
}
bool is_first_char_valid_alpha(const char *p)
{
return p != NULL && isalpha(p[0]); // 1) no unneeded isalpha() execution with p == NULL, 2) no SEGFAULT risk
}
Construção condicional idiomática
Como a avaliação mínima faz parte da definição semântica de um operador e não de uma otimização (opcional) , muitos padrões de codificação passaram a contar com ela como uma construção condicional sucinta (se idiomática). Exemplos incluem:
Expressões idiomáticas Perl :
some_condition or die; # Abort execution if some_condition is false
some_condition and die; # Abort execution if some_condition is true
Expressões idiomáticas de shell POSIX :
modprobe -q some_module && echo "some_module installed" || echo "some_module not installed"
Este idioma presume que echo
não pode falhar.
Possíveis problemas
A segunda condição não testada leva a um efeito colateral não realizado
Apesar desses benefícios, a avaliação mínima pode causar problemas para os programadores que não percebem (ou esquecem) o que está acontecendo. Por exemplo, no código
if (expressionA && myfunc(b)) {
do_something();
}
se myfunc(b)
deve realizar alguma operação necessária independentemente de do_something()
ser executada, como alocar recursos do sistema, e for expressionA
avaliada como falsa, então myfunc(b)
não será executado, o que pode causar problemas. Algumas linguagens de programação, como Java , possuem dois operadores, um que emprega avaliação mínima e outro que não, para evitar esse problema.
Problemas com declarações de efeitos colaterais não executadas podem ser facilmente resolvidos com estilo de programação adequado, ou seja, não usando efeitos colaterais em declarações booleanas, pois usar valores com efeitos colaterais em avaliações tende a tornar o código opaco e sujeito a erros.
Eficiência reduzida devido a otimizações restritivas
O curto-circuito pode levar a erros na previsão de ramificação em unidades de processamento central (CPUs) modernas e reduzir drasticamente o desempenho. Um exemplo notável é o raio altamente otimizado com código de interseção de caixa alinhada ao eixo no traçado de raio . Alguns compiladores podem detectar esses casos e emitir código mais rápido, mas a semântica da linguagem de programação pode restringir essas otimizações.
Um exemplo de um compilador incapaz de otimizar para esse caso é o Hotspot VM do Java em 2012.