0Pricing
Scala for Backend Engineering & Functional Programming · 课时

新类型

类型安全的包装器

新类型 是 CoddyKit 上的免费 Scala for Backend Engineering & Functional Programming 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Scala for Backend Engineering & Functional Programming 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Scala for Backend Engineering & Functional Programming 课程共包含 4 节课。

本课时的部分内容尚未翻译,以英文显示。

Type-Safe Wrappers

A newtype is a distinct type that wraps a single underlying value. It prevents mixing up values that share a primitive representation, like a UserId and a ProductId that are both Ints.

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

The Primitive Obsession Problem

Primitive obsession is overusing String and Int for domain concepts. It allows nonsense like passing an email where a name belongs. Newtypes fix this by giving each concept its own type.

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 Newtypes

The simplest newtype is a single-field case class. It gives equality, a readable toString, and pattern matching out of the box. The cost is one object allocation per value.

case class OrderId(value: String)

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

Opaque Type Newtypes

For zero allocation, implement a newtype with an opaque type. It behaves like the underlying type at runtime but is distinct at compile time.

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)

Adding Behavior with Extensions

Give a newtype operations through extension methods, exposing only what makes sense for the domain concept.

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)

Newtypes in Function Signatures

Newtypes make signatures self-documenting and catch argument-order bugs at compile time. You cannot accidentally swap two parameters of different newtypes.

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)))

Newtypes and Collections

A Map keyed by a newtype is clearer and safer than one keyed by a raw Int. The type prevents using the wrong kind of key.

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)))

Validated Newtypes

Combine a newtype with a smart constructor so the wrapper also enforces invariants. Here a NonEmptyString can never be empty.

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(""))

Choosing a Representation

Pick based on your needs.

  • case class: easiest, pattern-matchable, allocates an object.
  • opaque type: zero-cost, no boxing, ideal for hot paths and big collections.
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)

Preventing Accidental Conversions

Two newtypes over the same base do not implicitly convert. To go between them you write an explicit function, documenting the intent.

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)))

When to Use Newtypes

Reach for newtypes whenever a primitive carries domain meaning.

  • Identifiers, units, formatted strings.
  • Use case class for simplicity, opaque type for performance.
  • Add a smart constructor when there are invariants.
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))

Quick Check

Test your understanding of newtypes.

Recap

You learned newtypes.

  • Newtypes wrap one underlying value in a distinct type.
  • They cure primitive obsession and prevent argument mixups.
  • Use case class for simplicity, opaque type for zero cost.
  • Add a smart constructor for validated wrappers.
  • Conversions between newtypes are always explicit.
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)

常见问题解答

「新类型」课时是免费的吗?

是的 — 「新类型」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Scala for Backend Engineering & Functional Programming 课程的其余内容,请升级到 CoddyKit PRO。 Scala for Backend Engineering & Functional Programming 课程共包含 4 节课。

「新类型」这节课中我会学到什么?

类型安全的包装器 你通过在浏览器中直接运行的动手代码来练习 Scala for Backend Engineering & Functional Programming,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Scala for Backend Engineering & Functional Programming 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Scala for Backend Engineering & Functional Programming 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「新类型」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Scala for Backend Engineering & Functional Programming 课中编写并运行代码吗?

能。每节 Scala for Backend Engineering & Functional Programming 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 使用代数数据类型建模
  2. 智能构造器
  3. 新类型
  4. 组合领域模型
← 返回 Scala for Backend Engineering & Functional Programming