단락 평가

단락 평가(short-circuit evaluation), 최소 평가(minimal evaluation), 또는 매카시 평가(McCarthy evaluation, 존 매카시의 이름을 따서 명명됨)는 일부 프로그래밍 언어의 일부 논리 연산에서 두 번째 인수가 첫 번째 인수가 식의 값을 결정하기에 충분하지 않을 때만 실행되거나 평가되는 시맨틱이다. AND 함수의 첫 번째 인수가 false로 평가될 때 전체 값은 false여야 하며, OR 함수의 첫 번째 인수가 true로 평가될 때 전체 값은 true여야 한다.

느긋한 평가를 사용하는 프로그래밍 언어(리스프, 펄, 하스켈)에서는 일반적인 불리언 연산자가 단락된다. 다른 언어(에이다, 자바, 델파이)에서는 단락 연산자와 표준 불리언 연산자 모두 사용할 수 있다. 배타적 논리합(XOR)과 같은 일부 불리언 연산의 경우, 결과를 결정하기 위해 항상 두 피연산자가 모두 필요하므로 단락할 수 없다.

단락 연산자는 엄격하지 않으므로, 단순한 산술 연산자라기보다는 사실상 제어 구조이다. 부작용이 중요한 명령형 언어(C 및 C++ 특히)에서는 단락 연산자가 순서 지정점(sequence point)을 도입한다. 즉, 두 번째 인수를 (선택적으로) 처리하기 전에 모든 부작용을 포함하여 첫 번째 인수를 완전히 평가한다. 알골 68은 사용자 정의 단락 연산자 및 프로시저를 달성하기 위해 프로시저링(proceduring)을 사용했다.

단락 연산자의 사용은 문제점으로 비판받아 왔다.

 정의 
단락 평가를 구현하는 모든 프로그래밍 언어에서 x and y 식은 조건부 식 if x then y else x와 동일하며, x or y 식은 if x then x else y와 동일하다. 두 경우 모두 x는 한 번만 평가된다.

위의 일반화된 정의는 True와 False 두 가지 진리값보다 많은 값을 가지는 느슨한 유형의 언어를 수용하며, 단락 연산자는 마지막으로 평가된 하위 식을 반환할 수 있다. 이는 아래 표에서 "마지막 값"(last value)이라고 불린다. 엄격하게 유형이 지정된 언어의 경우, 불리언 식은 각각 if x then y else false 및 if x then true else y로 단순화된다.

 연산자 우선순위 
많은 언어에서 가 보다 우선순위가 높지만, 이것이 단락 평가의 보편적인 속성은 아니다. 두 연산자가 동일한 우선순위를 가지며 서로 왼쪽 결합성인 예는 POSIX 셸의 명령 목록 구문이다.

다음의 간단한 왼쪽에서 오른쪽으로 평가하는 평가기는 를 통해 가 보다 우선순위를 갖도록 강제한다.

 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

 형식화 
부작용이 있거나 없는 단락 논리는 호어의 조건문을 기반으로 형식화되었다. 결과적으로 비단락 연산자는 동일한 평가 시퀀스를 갖도록 단락 논리에서 정의될 수 있다.

 비트 연산자와의 비교 
& 및 |는 많은 프로그래밍 언어에 존재하는 비트 연산자이다. 주요 차이점은 비트 연산은 이진 숫자의 개별 비트에 대해 작동하는 반면, 조건부 연산은 논리 연산에 대해 작동한다는 점이다. 또한, 비트 연산자 양쪽의 식은 항상 평가된다. 자바 및 C#를 포함한 일부 언어에서는 불리언 피연산자에 사용하여 양쪽이 모두 평가되도록 강제할 수 있다.
if (expression1 || expression2 || expression3)expression 1이 참이면 expression 2와 3은 확인되지 않는다.
if (expression1 | expression2 | expression3)이는 expression 1이 참이더라도 expression 2와 3을 확인한다.

단락 연산자는 불필요한 계산을 피함으로써 실행 시간을 줄일 수 있다. 또한 expression 1이 객체가 유효한지 확인하는 경우 null 예외를 피할 수도 있다.

 일반적인 프로그래밍 및 스크립팅 언어 지원 
다음 표는 일반적인 프로그래밍 언어와 논리곱 AND 및 논리합 OR에 대한 기본 불리언 연산자로 제한된다. 비트 연산자는 부지런한 불리언 연산자로 사용될 수 있고 동일한 반환 유형을 갖는 언어에만 표시된다.

삼항 조건 연산자와 같이 더 많은 단락 연산자가 존재한다. 예를 들어 cond ? e1 : e2 (C, C++, 자바, PHP), if cond then e1 else e2 (알골, 하스켈, 코틀린, 러스트), e1 if cond else e2 (파이썬) 등이 있다. ?:#사용법을 참조하라.

+ 일반적인 언어의 불리언 연산자 언어  부지런한 연산자  단락 연산자  결과 유형 에이다 and, or and then, or else Boolean 알골 68 and, &, ∧ ; or, ∨  Boolean APL ∧, ∨ :AndIf, :OrIf Boolean awk  &&, || Boolean C, 오브젝티브-C &, | &&, ||ISO/IEC 9899 standard, section 6.5.13 int C++   &&, ||ISO/IEC IS 14882 draft. Boolean C# &, | &&, || Boolean D  &, | &&, || Boolean 에펠 and, or and then, or else Boolean 얼랭 and, or andalso, orelse Boolean 포트란  .and., .or. .and., .or. Boolean Go, 하스켈, OCaml   &&, || Boolean 자바, R, 스위프트 &, | &&, || Boolean 자바스크립트  &&, || Last value 줄리아  &&, || Last value 코틀린 and, or &&, || Boolean 리스프, 루아 , 스킴  and, or Last value 매트랩  &, | &, |, &&, || Boolean MUMPS (M) &, !  Numeric 모듈라-2  AND, OR Boolean 파스칼 and, or and_then, or_else Boolean 펄 &, | &&, and, ||, or Last value PHP  &&, and, ||, or Boolean POSIX 셸, 배시  &&, || Numeric (종료 코드) 파워셸 스크립팅 언어  -and, -or Boolean 파이썬 &, | and, or Last value 루비 &, | &&, and, ||, or Last value 러스트 &, | &&, || Boolean 스몰토크 &, | and:, or: Boolean 표준 ML  andalso, orelse Boolean 비주얼 베이직 .NET And, Or AndAlso, OrElse Boolean 비주얼 베이직, 비주얼 베이직 포 애플리케이션 (VBA) And, Or  Numeric

 일반적인 사용 
 두 번째 인수의 원치 않는 부작용 방지 
C 기반 언어를 사용하는 일반적인 예시:
int denom = 0;
if (denom != 0 && num / denom) {
    ... // num/denom 계산이 0으로 나누기 오류를 발생시키지 않도록 보장
}

다음 예시를 고려하라.
int a = 0;
if (a != 0 && myfunc(b)) {
    doSomething();
}

이 예시에서 단락 평가는 myfunc(b)가 절대 호출되지 않음을 보장한다. 이는 a != 0이 false로 평가되기 때문이다. 이 기능은 두 가지 유용한 프로그래밍 구조를 허용한다.

 첫 번째 하위 식이 비용이 많이 드는 계산이 필요한지 확인하고 그 확인이 false로 평가되면, 두 번째 인수에서 비용이 많이 드는 계산을 제거할 수 있다.
 첫 번째 식이 두 번째 식이 런타임 오류를 일으킬 수 있는 조건이 없음을 보장하는 구조를 허용한다.

두 가지 모두 다음 C 스니펫에 설명되어 있으며, 최소 평가는 널 포인터 역참조와 과도한 메모리 가져오기(memory fetches)를 모두 방지한다.
bool isFirstCharValidAlphaUnsafe(const char* p) {
    return isalpha(p[0]); // p == NULL일 때 SEGFAULT 발생 가능성 높음
}

bool isFirstCharValidAlpha(const char* p) {
    return p != NULL && isalpha(p[0]); // 1) p == NULL일 때 불필요한 isalpha() 실행 없음, 2) SEGFAULT 위험 없음
}

 관용적인 조건 구조 
최소 평가는 연산자의 시맨틱 정의의 일부이며 선택적인 컴파일러 최적화가 아니므로, 많은 코딩 관용구들이 간결한 조건 구조로 이를 사용한다. 예시는 다음과 같다.

펄 관용구:
some_condition or die;    # some_condition이 거짓이면 실행 중단
some_condition and die;   # some_condition이 참이면 실행 중단

POSIX 셸 관용구:
modprobe -q some_module && echo "some_module installed" || echo "some_module not installed"
이 관용구는 echo가 실패할 수 없다고 가정한다.

 발생 가능한 문제점 
 테스트되지 않은 두 번째 조건으로 인한 부작용 미수행 
이러한 이점에도 불구하고 최소 평가는 자신이 발생하고 있음을 깨닫지 못하는(또는 잊어버리는) 프로그래머에게 문제를 일으킬 수 있다. 예를 들어, 다음 코드에서
if (expressionA && myFunc(b)) {
    doSomething();
}
만약 myFunc(b)가 doSomething() 실행 여부와 관계없이 시스템 자원을 할당하는 것과 같은 필수 작업을 수행해야 하고, expressionA가 거짓으로 평가되면, myFunc(b)는 실행되지 않아 문제가 발생할 수 있다. 자바와 같은 일부 프로그래밍 언어는 이 문제를 피하기 위해 최소 평가를 사용하는 연산자와 그렇지 않은 연산자 두 가지를 가지고 있다.

부작용이 있는 문으로 인한 문제는 적절한 프로그래밍 스타일, 즉 불리언 문에서 부작용을 사용하지 않음으로써 쉽게 해결할 수 있다. 평가에서 부작용이 있는 값을 사용하면 일반적으로 코드가 불투명하고 오류가 발생하기 쉽기 때문이다.

 최적화 제약으로 인한 효율성 감소 
단락은 최신 중앙 처리 장치(CPU)에서 분기 예측 오류로 이어질 수 있으며 성능을 크게 저하시킬 수 있다. 특히 광선 추적에서 축 정렬 박스 교차 코드(axis aligned box intersection code)를 이용한 고도로 최적화된 광선에서 두드러진다. 일부 컴파일러는 이러한 경우를 감지하고 더 빠른 코드를 생성할 수 있지만, 프로그래밍 언어 의미론이 이러한 최적화를 제한할 수 있다.

자바의 핫스팟 가상 머신(VM)은 2012년 현재 이러한 경우에 대해 최적화할 수 없는 컴파일러의 한 예이다.

 같이 보기 
 돈트 케어 항
 널 병합 연산자
 ?:

 각주 

분류:컴파일러 최적화
분류:조건문
분류:평가 전략