비교 연산자

비교 연산자

비교 연산자는 두 개의 피연산자를 비교해서 untyped boolean 값을 만들어 내요. ==로 값이 같은지, <로 어느 쪽이 더 작은지를 묻는다고 이해하면 되죠. 여섯 개의 비교 연산자와 그 의미부터 볼게요.

출처: Go Specification

본문

==    equal
!=    not equal
<     less
<=    less or equal
>     greater
>=    greater or equal

어떤 비교든 첫 번째 피연산자가 두 번째 피연산자의 타입에 assignable해야 해요. 아니면 반대 방향으로 assignable이어야 하고요. 즉 비교하려면 두 값이 서로 같은 타입 체계 안에 있을 수 있어야 한다는 뜻이에요.

등가 연산자 ==!=comparable 타입의 피연산자에 적용돼요. 순서 연산자 <, <=, >, >=ordered 타입의 피연산자에 적용되고요. 이제 이 용어들과 비교 결과가 타입별로 어떻게 정해지는지 살펴볼게요.

  • 불리언 타입은 comparable해요. 두 불리언 값은 둘 다 true이거나 둘 다 false일 때 같다고 봐요.
  • 정수 타입은 comparable하고 ordered예요. 두 정수 값은 일반적인 방식으로 비교해요.
  • 부동소수점 타입은 comparable하고 ordered예요. 두 부동소수점 값은 IEEE 754 표준에 정의된 대로 비교해요.
  • 복소수 타입은 comparable해요. 두 복소수 값 uvreal(u) == real(v)이고 imag(u) == imag(v)일 때 같아요.
  • 문자열 타입은 comparable하고 ordered예요. 두 문자열 값은 바이트 단위로 사전식(lexically) 비교해요.
  • 포인터 타입은 comparable해요. 두 포인터 값은 같은 변수를 가리키거나 둘 다 값이 nil일 때 같아요. 서로 다른 zero-size 변수를 가리키는 포인터는 같을 수도 있고 다를 수도 있어요.
  • 채널 타입은 comparable해요. 두 채널 값은 같은 make 호출로 만들어졌거나 둘 다 값이 nil일 때 같아요.
  • 타입 파라미터가 아닌 인터페이스 타입은 comparable해요. 두 인터페이스 값은 동일한(identical) dynamic 타입을 가지면서 dynamic 값도 같거나, 둘 다 값이 nil일 때 같아요.
  • 비-인터페이스 타입 X의 값 x인터페이스 타입 T의 값 t 는, 타입 X가 comparable하고 XTimplements할 때 비교할 수 있어요. t의 dynamic 타입이 X와 동일하고 t의 dynamic 값이 x와 같을 때 두 값은 같다고 봐요.
  • 구조체 타입은 모든 필드 타입이 comparable할 때 comparable해요. 두 구조체 값은 대응하는 non-blank 필드 값이 같을 때 같아요. 필드는 소스 순서대로 비교하고, 두 필드 값이 달라지는 순간 비교가 멈춰요(혹은 모든 필드를 다 비교하면 끝나요).
  • 배열 타입은 배열 요소 타입이 comparable할 때 comparable해요. 두 배열 값은 대응하는 요소 값이 같을 때 같아요. 요소는 인덱스가 오름차순으로 비교되고, 두 요소 값이 달라지는 순간 멈춰요(혹은 모든 요소를 다 비교하면 끝나요).
  • 타입 파라미터는 strictly comparable할 때 comparable해요. 이건 아래에서 설명할게요.

동일한 dynamic 타입을 가진 두 인터페이스 값을 비교했는데 그 타입이 comparable하지 않으면 run-time panic이 발생해요. 이 동작은 인터페이스 값의 직접 비교에만 해당하는 게 아니라, 인터페이스 값의 배열이나 인터페이스 값 필드를 가진 구조체를 비교할 때에도 똑같이 적용되요.

슬라이스, 맵, 함수 타입은 comparable하지 않아요. 다만 특별한 경우로, 슬라이스·맵·함수 값은 미리 선언된 식별자 nil과 비교할 수 있어요. 포인터·채널·인터페이스 값을 nil과 비교하는 것도 허용되는데, 이건 위의 일반 규칙에서 자연스럽게 따라 나와요.

비교 결과가 어떤 타입으로 굴러가는지 코드로 확인해 볼게요. 비교의 결과는 untyped boolean이라서 일반적인 대입 규칙이 그대로 적용돼요.

const c = 3 < 4            // c is the untyped boolean constant true

type MyBool bool
var x, y int
var (
	// The result of a comparison is an untyped boolean.
	// The usual assignment rules apply.
	b3        = x == y // b3 has type bool
	b4 bool   = x == y // b4 has type bool
	b5 MyBool = x == y // b5 has type MyBool
)

여기서 다뤄야 할 개념이 하나 더 있어요. strictly comparable이라는 건데요, comparable하면서 인터페이스 타입도 아니고 인터페이스 타입으로 구성되지도 않은 타입을 말해요. 구체적으로 보면 이래요.

  • 불리언, numeric, 문자열, 포인터, 채널 타입은 strictly comparable해요.
  • 구조체 타입은 모든 필드 타입이 strictly comparable할 때 strictly comparable해요.
  • 배열 타입은 배열 요소 타입이 strictly comparable할 때 strictly comparable해요.
  • 타입 파라미터는 타입 집합의 모든 타입이 strictly comparable할 때 strictly comparable해요.

더 알아보기

  • Assignability — 피연산자가 서로 비교 가능해지려면 어떤 대입 관계가 성립해야 하는지.
  • Type identity — 인터페이스 값 비교에서 말하는 '동일한 dynamic 타입'의 기준.
  • Run-time panics — comparable하지 않은 타입을 인터페이스 값으로 비교할 때 발생하는 패닉.