교차 타입
교차 타입 (Intersection Types)
타입에 사용되는 & 연산자는 소위 *교차 타입(intersection type)*을 만들어 냅니다. 타입 A & B는 타입 A이면서 동시에 타입 B인 값을 나타내요. 예를 들어 다음 예시는 교차 타입 Resettable & Growable[String]을 사용해요.
trait Resettable:
def reset(): Unit
trait Growable[A]:
def add(a: A): Unit
def f(x: Resettable & Growable[String]): Unit =
x.reset()
x.add("first")
이 예시의 메서드 f에서 파라미터 x는 Resettable이면서 Growable[String]이어야 해요.
교차 타입 A & B의 멤버는 A의 모든 멤버와 B의 모든 멤버예요. 따라서 위에서 보듯 Resettable & Growable[String]은 reset과 add 멤버 메서드를 가져요.
교차 타입은 요구사항을 구조적으로(structurally) 기술하는 데 유용해요. 즉, 우리의 예시 f에서 x는 Resettable과 Growable 둘 다의 하위 타입이기만 하면 어떤 값이든 괜찮다고 직접 표현하는 거예요. 아래 같은 명목적(nominal) 헬퍼 트레잇을 만들 필요가 전혀 없어요.
trait Both[A] extends Resettable, Growable[A]
def f(x: Both[String]): Unit
f를 정의하는 두 방식 사이에는 중요한 차이가 있어요. 둘 다 Both의 인스턴스로 f를 호출하는 것을 허용하지만, 전자만 Resettable과 Growable[String]의 하위 타입이면서 Both[String]의 하위 타입은 아닌 인스턴스를 넘기는 것을 허용해요.
또한 &는 *교환 법칙(commutative)*이 성립해요. A & B는 B & A와 같은 타입이에요.
출처: Scala 3 Book
본문
교차 타입은 여러 트레잇의 요구사항을 하나로 합쳐 표현하고 싶을 때 특히 자연스러워요. 명목적인 이름을 가진 헬퍼 트레잇을 만들지 않고도 "이 값은 두 타입을 모두 만족해야 한다"는 조건을 그대로 타입에 담을 수 있거든요.
이런 성격 때문에 교차 타입은 주로 파라미터 타입이나 추상 타입의 경계에서, 어떤 값이 여러 트레잇의 계약을 동시에 지켜야 할 때 쓰여요. 같은 문제를 헬퍼 트레잇으로 푸는 것과 비교하면, 헬퍼 트레잇은 인스턴스가 정확히 그 트레잇을 상속하도록 강제하는 반면 교차 타입은 하위 타입 관계만 만족하면 되니 더 유연하죠.