0Pricing
Scala for Backend Engineering & Functional Programming · 강의

새 타입

타입 안전 래퍼

새 타입은(는) CoddyKit의 무료 Scala for Backend Engineering & Functional Programming 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Scala for Backend Engineering & Functional Programming 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Scala for Backend Engineering & Functional Programming 강의에는 총 4개의 강의가 포함되어 있습니다.

타입 안전 래퍼

뉴타입은 하나의 기반 값을 감싸는 별개의 타입입니다. UserId와 ProductId처럼 원시 표현은 같은 Int인 값들을 서로 혼동하지 않게 합니다.

case class UserId(value: Int)
case class ProductId(value: Int)

object Main:
  def main(args: Array[String]): Unit =
    val u = UserId(1)
    val p = ProductId(1)
    println(u.value == p.value) // values equal, types differ

원시 타입 과용 문제

원시 타입 과용은 도메인 개념에 String과 Int를 지나치게 사용하는 것입니다. 이 경우 이름이 들어가야 할 자리에 이메일을 전달하는 등의 말이 안 되는 실수가 발생할 수 있습니다. 뉴타입은 각 개념에 고유한 타입을 부여해 이 문제를 해결합니다.

case class Email(value: String)
case class City(value: String)

object Main:
  def greet(c: City): String = s"Welcome to ${c.value}"

  def main(args: Array[String]): Unit =
    println(greet(City("Berlin")))

케이스 클래스 뉴타입

가장 간단한 뉴타입은 필드 하나를 가진 case class입니다. 기본적으로 동등성, 읽기 쉬운 toString, 패턴 일치를 제공합니다. 대신 값마다 객체 하나를 할당하는 비용이 발생합니다.

case class OrderId(value: String)

object Main:
  def main(args: Array[String]): Unit =
    val id = OrderId("ORD-42")
    println(id)
    println(id.value)

불투명 타입 뉴타입

할당을 없애려면 불투명 타입으로 뉴타입을 구현합니다. 실행 시에는 기반 타입처럼 동작하지만 컴파일 시에는 별개의 타입입니다.

object Ids:
  opaque type UserId = Int
  def apply(i: Int): UserId = i
  extension (u: UserId) def value: Int = u

object Main:
  def main(args: Array[String]): Unit =
    val u = Ids(7)
    println(u.value)

확장으로 동작 추가

extension 메서드를 통해 뉴타입에 연산을 추가하고, 도메인 개념에 의미 있는 기능만 노출합니다.

object Money:
  opaque type Cents = Long
  def apply(n: Long): Cents = n
  extension (c: Cents)
    def +(o: Cents): Cents = c + o
    def toDollars: Double = c / 100.0

object Main:
  def main(args: Array[String]): Unit =
    val total = Money(250) + Money(750)
    println(total.toDollars)

함수 시그니처에서의 뉴타입

뉴타입은 시그니처 자체가 의미를 설명하게 하고 컴파일 시 인수 순서 오류를 찾아냅니다. 서로 다른 뉴타입의 두 매개변수를 실수로 뒤바꿀 수 없습니다.

case class Width(value: Int)
case class Height(value: Int)

object Main:
  def area(w: Width, h: Height): Int = w.value * h.value

  def main(args: Array[String]): Unit =
    println(area(Width(4), Height(5)))

뉴타입과 컬렉션

뉴타입을 키로 사용하는 Map은 원시 Int를 키로 사용하는 Map보다 명확하고 안전합니다. 타입이 잘못된 종류의 키를 사용하는 것을 막습니다.

case class UserId(value: Int)

object Main:
  def main(args: Array[String]): Unit =
    val names = Map(UserId(1) -> "Ada", UserId(2) -> "Bob")
    println(names(UserId(2)))

검증된 뉴타입

뉴타입을 스마트 생성자와 결합하면 래퍼가 불변 조건도 적용하게 됩니다. 여기서 NonEmptyString은 절대 비어 있을 수 없습니다.

case class NonEmptyString private (value: String)
object NonEmptyString:
  def of(s: String): Option[NonEmptyString] =
    if s.nonEmpty then Some(NonEmptyString(s)) else None

object Main:
  def main(args: Array[String]): Unit =
    println(NonEmptyString.of("hi"))
    println(NonEmptyString.of(""))

표현 방식 선택

필요에 따라 선택합니다.

  • case class: 가장 쉽고 패턴 일치가 가능하지만 객체를 할당합니다.
  • opaque type: 비용이 없고 박싱이 발생하지 않으며, 성능이 중요한 경로와 대규모 컬렉션에 적합합니다.
object Temp:
  opaque type Kelvin = Double
  def apply(d: Double): Kelvin = d
  extension (k: Kelvin) def value: Double = k

object Main:
  def main(args: Array[String]): Unit =
    val readings = List(Temp(300.0), Temp(310.5))
    println(readings.map(_.value).sum)

암묵적 변환 방지

같은 기반 타입을 사용하는 두 뉴타입은 암묵적으로 변환되지 않습니다. 두 타입 사이를 변환하려면 의도를 문서화하는 명시적 함수를 작성해야 합니다.

case class Meters(value: Double)
case class Feet(value: Double)

object Main:
  def toFeet(m: Meters): Feet = Feet(m.value * 3.281)

  def main(args: Array[String]): Unit =
    println(toFeet(Meters(2.0)))

뉴타입을 사용하는 경우

원시 타입에 도메인 의미가 담길 때마다 뉴타입을 사용합니다.

  • 식별자, 단위, 형식이 지정된 문자열에 사용합니다.
  • 단순함이 중요하면 케이스 클래스를, 성능이 중요하면 불투명 타입을 사용합니다.
  • 불변 조건이 있으면 스마트 생성자를 추가합니다.
object Domain:
  opaque type Sku = String
  def of(s: String): Option[Sku] =
    if s.startsWith("SKU-") then Some(s) else None
  extension (k: Sku) def raw: String = k

object Main:
  def main(args: Array[String]): Unit =
    println(Domain.of("SKU-9").map(_.raw))

빠른 확인

뉴타입에 대한 이해도를 확인해 보세요.

복습

뉴타입을 배웠습니다.

  • 뉴타입은 하나의 기반 값을 별개의 타입으로 감쌉니다.
  • 원시 타입 과용을 해결하고 인수 혼동을 방지합니다.
  • 단순함에는 case class를, 비용이 없는 구현에는 opaque type을 사용합니다.
  • 검증된 래퍼에는 스마트 생성자를 추가합니다.
  • 뉴타입 간 변환은 항상 명시적으로 이루어집니다.
object Ids:
  opaque type AccountId = Long
  def apply(n: Long): AccountId = n
  extension (a: AccountId) def value: Long = a

object Main:
  def main(args: Array[String]): Unit =
    val acc = Ids(1001L)
    println(acc.value)

자주 묻는 질문

“새 타입” 강의는 무료인가요?

네 — “새 타입” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Scala for Backend Engineering & Functional Programming 강의 전체를 잠금 해제할 수 있습니다. Scala for Backend Engineering & Functional Programming 강의에는 총 4개의 강의가 포함되어 있습니다.

“새 타입”에서 뭘 배우나요?

타입 안전 래퍼 브라우저에서 직접 실행하는 실습 코드로 Scala for Backend Engineering & Functional Programming을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

Scala for Backend Engineering & Functional Programming을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 Scala for Backend Engineering & Functional Programming은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.

“새 타입” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 Scala for Backend Engineering & Functional Programming 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 Scala for Backend Engineering & Functional Programming 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. ADT로 모델링하기
  2. 스마트 생성자
  3. 새 타입
  4. 도메인 조합하기
← Scala for Backend Engineering & Functional Programming(으)로 돌아가기