Avaliação de curto-circuito - Short-circuit evaluation

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 ANDfunção é avaliado como false, o valor geral deve ser false; e quando o primeiro argumento da ORfunçã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 xx or yif 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 falseif x then true else y

Precedência

Embora ANDtoma precedência sobre ORem 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 ANDsobre ORpor 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

Operadores booleanos em vários idiomas
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, or5 , 9 and_then, or_else6 , 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 Case8 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 ife 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_thene 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 ANDe ORusa avaliação de curto-circuito e não define os operadores AND_THENe 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 != 0avaliada como falsa . Este recurso permite duas construções de programação úteis.

  1. 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.
  2. 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 echonã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 expressionAavaliada 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.

Veja também

Referências