교집합 타입
교집합 타입 (Intersection Types)
타입에 & 연산자를 쓰면 **교집합 타입(intersection type)**이 만들어져요. S & T 타입은 타입 S이면서 동시에 타입 T인 값을 나타내죠.
trait Resettable:
def reset(): Unit
trait Growable[T]:
def add(t: T): Unit
def f(x: Resettable & Growable[String]) =
x.reset()
x.add("first")
본문
타입 검사 (Type Checking)
위 예시에서 파라미터 x는 Resettable이면서 동시에 Growable[String]이어야 해요. 그래서 x.reset()과 x.add("first")를 모두 호출할 수 있는 거죠.
교집합 타입 A & B의 멤버는 A의 모든 멤버와 B의 모든 멤버예요. 예를 들어 Resettable & Growable[String]에는 메서드 reset과 add가 멤버로 있죠.
&는 교환 법칙이 성립해요. A & B는 B & A와 같은 타입이에요.
만약 어떤 멤버가 A와 B 양쪽에 모두 나타난다면, A & B에서 그 멤버의 타입은 A에서의 타입과 B에서의 타입의 교집합이 돼요. 예를 들어 다음 정의를 생각해 볼게요:
trait A:
def children: List[A]
trait B:
def children: List[B]
val x: A & B = new C
val ys: List[A & B] = x.children
A & B에서 children의 타입은 A에서의 children 타입과 B에서의 타입의 교집합, 즉 List[A] & List[B]가 돼요. 여기서 List는 공변(covariant)이라서 List[A & B]로 더 단순화할 수 있죠.
궁금할 수 있는 점이 하나 있어요. 주어진 건 각각 List[A] 타입과 List[B] 타입의 children 정의인데, 컴파일러는 어떻게 List[A & B] 타입의 children 정의를 만들어 낼까요? 답은 간단해요. 컴파일러가 그럴 필요가 없다는 거예요. A & B는 그저 "그 타입의 값이 지켜야 할 요구사항들의 집합"을 나타내는 타입일 뿐이거든요. 값이 실제로 만들어지는 지점에서, 모든 상속된 멤버가 올바르게 정의되도록 보장하면 돼요. 그래서 A와 B를 상속하는 클래스 C를 정의한다면, 그 지점에서 필요한 타입의 children 메서드를 직접 정의해 줘야 해요:
class C extends A, B:
def children: List[A & B] = ???