합집합 타입
합집합 타입 (Union Types)
타입에 | 연산자를 쓰면 이른바 **합집합 타입(union type)**이 만들어져요. A | B 타입은 A 타입이거나 B 타입인 값을 나타내죠.
다음 예제에서 help 메서드는 Username | Password라는 합집합 타입의 id 파라미터를 받는데, 그 값은 Username이거나 Password일 수 있어요:
case class Username(name: String)
case class Password(hash: Hash)
def help(id: Username | Password) =
val user = id match
case Username(name) => lookupName(name)
case Password(hash) => lookupPassword(hash)
// more code here ...
help 메서드는 패턴 매칭으로 두 대안을 구분해서 구현해요.
이 코드는 유연하고 타입 안전한 해법이에요. Username이나 Password 외의 타입을 넘기려 하면 컴파일러가 오류로 표시하죠:
help("hi") // error: Found: ("hi" : String)
// Required: Username | Password
match 표현식에 Username이나 Password 타입과 맞지 않는 케이스를 추가하려 해도 오류가 나요:
case 1.0 => ??? // ERROR: this line won’t compile
출처: Scala 3 Book
본문
합집합 타입의 대안
앞서 봤듯이 합집합 타입은 서로 다른 여러 타입의 대안을 나타내는 데 쓸 수 있어요. 그 타입들이 직접 짠 클래스 계층의 일부일 필요도, 명시적인 감싸기(wrapping)가 필요하지도 않죠.
클래스 계층을 미리 설계하는 방법
합집합 타입이 없다면 클래스 계층을 미리 설계해야 해요. 다음 예제가 그걸 보여줘요:
trait UsernameOrPassword
case class Username(name: String) extends UsernameOrPassword
case class Password(hash: Hash) extends UsernameOrPassword
def help(id: UsernameOrPassword) = ...
미리 설계하는 방식은 잘 확장되지 않아요. 예를 들어 API 사용자의 요구사항을 미리 예측할 수 없는 경우가 있으니까요. 게다가 UsernameOrPassword 같은 마커 트레이트로 타입 계층을 어지럽히는 것은 코드를 읽기 더 어렵게 만들어요.
태그된 합집합(Tagged Unions)
또 다른 대안은 다음과 같은 별도의 enumeration 타입을 정의하는 거예요:
enum UsernameOrPassword:
case IsUsername(u: Username)
case IsPassword(p: Password)
enum UsernameOrPassword는 Username과 Password의 태그된 합집합을 나타내요. 하지만 이런 방식으로 합집합을 모델링하려면 명시적인 감싸기와 풀기가 필요하고, 예를 들어 Username은 UsernameOrPassword의 하위 타입이 아니에요.
합집합 타입의 추론
컴파일러는 표현식에 합집합 타입이 명시적으로 주어졌을 때만 그 타입을 할당해요. 예를 들어 이런 값들이 있다고 할게요:
val name = Username("Eve") // name: Username = Username(Eve)
val password = Password(123) // password: Password = Password(123)
이 REPL 예제는 if/else 표현식의 결과를 변수에 바인딩할 때 합집합 타입을 쓸 수 있음을 보여줘요:
scala> val a = if true then name else password
val a: Object = Username(Eve)
scala> val b: Password | Username = if true then name else password
val b: Password | Username = Username(Eve)
a의 타입은 Object인데, 이는 Username과 Password의 슈퍼타입이지만 최소 슈퍼타입(Password | Username)은 아니에요. 최소 슈퍼타입을 원한다면 b에서처럼 명시적으로 주어야 합니다.
합집합 타입은 교집합 타입(intersection type)의 쌍대(dual)예요. 그리고 교집합의 &처럼, 합집합의 |도 교환 법칙이 성립해요: A | B는 B | A와 같은 타입입니다.
더 알아보기
- 교집합 타입(intersection types)에 대한 설명은 "Intersection Types" 페이지에서 다룹니다.
- 패턴 매칭은 "Control Structures" 페이지에서 더 자세히 볼 수 있어요.