전체 비유: 건축 설계 시스템#

고급 타입 시스템을 건축 설계 규칙에 비유하면 이해하기 쉽습니다:

건축 설계 비유Scala 개념역할
“거실 또는 침실”Union Type (A | B)여러 타입 중 하나
“내진 설계 + 방음 설계”Intersection (A & B)여러 조건 동시 충족
“비공개 내부 설계도”Opaque Type외부에 구현 숨김
“조건부 자재 선택”Match Type타입 레벨 조건 분기
“자재 변환 규칙”Type Lambda타입 생성자 매핑
“방 크기에 따른 자재”Dependent Type값에 따른 타입 결정

건축에서 설계 규칙이 안전한 건물을 보장하듯이, 고급 타입 시스템은 컴파일 타임에 더 정밀한 제약을 표현하여 런타임 오류를 사전에 방지합니다.

TL;DR
  • Union Types (|): 여러 타입 중 하나, Int | String
  • Intersection Types (&): 여러 타입 모두 만족, A & B
  • Opaque Types: 런타임 오버헤드 없는 타입 안전성
  • Match Types: 타입 수준 패턴 매칭
  • Scala 3 전용 기능으로 더 강력한 타입 표현 가능

소요 시간: 약 20분

대상 독자: 제네릭과 변성을 이해한 Scala 개발자 선수 지식: 타입 매개변수, 타입 경계, 변성

Scala 3는 더욱 강력하고 표현력 있는 타입 시스템을 제공합니다. 이 문서에서는 Scala 3의 새로운 타입 기능을 다룹니다.

📚 사전 지식: 이 문서를 이해하려면 다음 개념에 익숙해야 합니다:

난이도: ⭐⭐⭐⭐ (고급) - Scala 3 전용 기능 포함

Union Types (|)#

Union Type은 여러 타입 중 하나의 값을 가질 수 있음을 나타냅니다. 래퍼 없이 직접 값을 다룰 수 있어 Either보다 간결합니다.

// Scala 3만 지원
def process(input: Int | String): String = input match
  case i: Int    => s"숫자: $i"
  case s: String => s"문자열: $s"

process(42)        // "숫자: 42"
process("hello")   // "문자열: hello"

// 여러 타입
type JsonValue = String | Int | Boolean | Null
def toJson(value: JsonValue): String = value match
  case s: String  => s"\"$s\""
  case i: Int     => i.toString
  case b: Boolean => b.toString
  case null       => "null"

Either와의 비교

// Either: 명시적인 Left/Right 래퍼 필요
def divideEither(a: Int, b: Int): Either[String, Int] =
  if b == 0 then Left("0으로 나눌 수 없음") else Right(a / b)

// Union: 래퍼 없이 직접 값 반환
def divideUnion(a: Int, b: Int): Int | String =
  if b == 0 then "0으로 나눌 수 없음" else a / b
핵심 포인트
  • Union Type은 A | B 형태로 여러 타입 중 하나를 표현
  • Either와 달리 래퍼 없이 직접 값을 다룸
  • 패턴 매칭으로 타입을 구분하여 처리

Intersection Types (&)#

Intersection Type은 여러 타입을 모두 만족하는 타입입니다. 객체가 여러 trait를 동시에 구현해야 할 때 유용합니다.

trait Printable:
  def print(): String

trait Serializable:
  def serialize(): Array[Byte]

// Printable이면서 Serializable인 타입
def process(obj: Printable & Serializable): Unit =
  println(obj.print())
  val bytes = obj.serialize()

// 구현
class Document(content: String) extends Printable, Serializable:
  def print(): String = content
  def serialize(): Array[Byte] = content.getBytes

process(Document("Hello"))

구조적 타입 결합

type Named = { def name: String }
type Aged = { def age: Int }

def describe(obj: Named & Aged): String =
  s"${obj.name}, ${obj.age}세"
핵심 포인트
  • Intersection Type은 A & B 형태로 여러 타입을 모두 만족
  • 객체가 여러 trait를 동시에 구현해야 할 때 사용
  • 구조적 타입과 결합하여 유연한 타입 정의 가능

Opaque Types#

Opaque Type은 타입 별칭이지만 외부에서는 다른 타입으로 취급됩니다. 런타임 오버헤드 없이 타입 안전성을 제공하여 도메인 모델링에 매우 유용합니다.

object UserId:
  opaque type UserId = Long

  def apply(id: Long): UserId = id

  extension (id: UserId)
    def value: Long = id
    def isValid: Boolean = id > 0

import UserId.*

val id: UserId = UserId(42)
id.value       // 42
id.isValid     // true

// val x: Long = id  // 컴파일 에러! UserId != Long

장점

  • 런타임 오버헤드 없음 (박싱 없음)
  • 타입 안전성 제공
  • 도메인 모델링에 유용
object Money:
  opaque type USD = BigDecimal
  opaque type EUR = BigDecimal

  def usd(amount: BigDecimal): USD = amount
  def eur(amount: BigDecimal): EUR = amount

  extension (x: USD)
    def +(y: USD): USD = x + y
    def value: BigDecimal = x

import Money.*

val dollars = usd(100)
val more = usd(50)
val total = dollars + more  // OK: USD + USD

// val mixed = dollars + eur(50)  // 컴파일 에러! USD + EUR
핵심 포인트
  • Opaque Type은 외부에서는 다른 타입으로 취급되는 타입 별칭
  • 런타임 오버헤드 없이 타입 안전성 제공 (박싱 없음)
  • 도메인 모델링에서 타입 혼동 방지에 유용

Match Types#

Match Type은 타입 레벨에서 패턴 매칭을 수행합니다. 입력 타입에 따라 다른 결과 타입을 계산할 수 있습니다.

type Elem[X] = X match
  case String      => Char
  case Array[t]    => t
  case Iterable[t] => t

val a: Elem[String] = 'a'        // Char
val b: Elem[Array[Int]] = 1      // Int
val c: Elem[List[String]] = "hi" // String

재귀 Match Types

type Flatten[X] = X match
  case List[List[t]] => Flatten[List[t]]
  case List[t]       => List[t]

// List[List[List[Int]]] -> List[Int]
val x: Flatten[List[List[List[Int]]]] = List(1, 2, 3)
핵심 포인트
  • Match Type은 타입 수준에서 패턴 매칭 수행
  • 입력 타입에 따라 다른 결과 타입 계산
  • 재귀적 Match Types로 복잡한 타입 변환 가능

Type Lambdas#

Type Lambda는 타입 생성자를 타입 매개변수로 전달할 수 있게 해줍니다. 고차 타입을 다룰 때 유용합니다.

// 타입 람다: [X] =>> F[X, Y]
type IntMap = [V] =>> Map[Int, V]

val map: IntMap[String] = Map(1 -> "one", 2 -> "two")
// 실제로는 Map[Int, String]

// Either를 고정된 에러 타입으로
type Result = [A] =>> Either[String, A]

val ok: Result[Int] = Right(42)
val err: Result[Int] = Left("에러")
핵심 포인트
  • Type Lambda는 [X] =>> F[X] 형태로 타입 생성자 표현
  • 고차 타입을 다룰 때 타입 매개변수 부분 적용에 유용
  • Scala 2의 복잡한 문법을 간결하게 대체

Dependent Function Types#

Dependent Function Type은 반환 타입이 매개변수 값에 의존하는 함수 타입입니다.

trait Key:
  type Value

val intKey = new Key { type Value = Int }
val strKey = new Key { type Value = String }

// 반환 타입이 key의 Value 타입에 의존
def get(key: Key): key.Value = ???

// 의존 함수 타입
val getter: (key: Key) => key.Value = (key: Key) => ???

Polymorphic Function Types#

Polymorphic Function Type은 타입 매개변수를 가진 함수 타입입니다. 함수 값 자체가 제네릭할 수 있습니다.

// 다형 함수 타입
val identity: [A] => A => A = [A] => (a: A) => a

identity[Int](42)       // 42
identity[String]("hi")  // "hi"

// 리스트의 첫 번째 요소
val head: [A] => List[A] => A = [A] => (list: List[A]) => list.head

head(List(1, 2, 3))  // 1

Scala 2와의 비교#

아래 표는 Scala 2와 Scala 3의 고급 타입 기능 차이를 정리한 것입니다.

기능Scala 2Scala 3
Union TypesEither 사용A | B
IntersectionA with BA & B
Opaque TypesValue Classopaque type
Match Types불가지원
Type Lambdas복잡한 문법[X] =>> F[X]

연습 문제#

1. Opaque Type 구현 ⭐⭐

Email opaque type을 구현하세요. 유효성 검사를 포함해야 합니다.

정답 보기
object Email:
  opaque type Email = String

  def apply(value: String): Option[Email] =
    if value.contains("@") && value.contains(".") then Some(value)
    else None

  def unsafe(value: String): Email = value

  extension (email: Email)
    def value: String = email
    def domain: String = email.split("@").last

import Email.*

val valid = Email("user@example.com")   // Some(Email)
val invalid = Email("invalid")          // None

valid.foreach(e => println(e.domain))   // "example.com"

관련 개념#

고급 타입 시스템은 다음 개념들과 밀접하게 연결됩니다:

관련 개념연결 관계
제네릭타입 매개변수와 타입 경계 기초
공변성/반공변성타입 변성과 Union/Intersection의 관계
패턴 매칭Match Types와 값 레벨 패턴 매칭 비교
타입 클래스Type Lambda로 고차 타입 클래스 정의
매크로타입 레벨 프로그래밍과 메타프로그래밍

다음 단계#

학습 경로설명
매크로와 메타프로그래밍컴파일 타임 코드 생성
함수형 패턴Functor, Monad 등 추상화
Scala 3 Reference공식 타입 시스템 문서