scalaadvanced
Phantom Types for Compile-Time Safety
Use phantom types to encode state and constraints at the type level without runtime cost.
scalaPress ⌘/Ctrl + Shift + C to copy
// Phantom type markers (never instantiated)
sealed trait Locked
sealed trait Unlocked
sealed trait Unverified
sealed trait Verified
// Door that can only be opened when unlocked
class Door[State] private (val name: String):
override def toString: String = s"Door($name)"
object Door:
def locked(name: String): Door[Locked] = Door(name)
extension (door: Door[Locked])
def unlock: Door[Unlocked] =
println(s" Unlocking ${door.name}")
Door(door.name)
extension (door: Door[Unlocked])
def lock: Door[Locked] =
println(s" Locking ${door.name}")
Door(door.name)
def open: String =
s" Opening ${door.name}"
// Email that must be verified before sending
case class Email[Status] private (to: String, subject: String, body: String)
object Email:
def draft(to: String, subject: String, body: String): Email[Unverified] =
Email(to, subject, body)
extension (email: Email[Unverified])
def verify: Either[String, Email[Verified]] =
if email.to.contains("@") && email.subject.nonEmpty then
Right(Email(email.to, email.subject, email.body))
else Left("Invalid email")
extension (email: Email[Verified])
def send: String = s"Sent to ${email.to}: ${email.subject}"
// Builder with phantom types
sealed trait HasHost
sealed trait HasPort
sealed trait NeedsHost
sealed trait NeedsPort
class ServerConfig[H, P] private (
val host: String,
val port: Int,
val ssl: Boolean
):
override def toString = s"$host:$port (ssl=$ssl)"
object ServerConfig:
def builder: ServerConfig[NeedsHost, NeedsPort] =
ServerConfig("", 0, false)
extension (b: ServerConfig[NeedsHost, NeedsPort])
def withHost(h: String): ServerConfig[HasHost, NeedsPort] =
ServerConfig(h, b.port, b.ssl)
extension [P](b: ServerConfig[HasHost, P])
def withSSL: ServerConfig[HasHost, P] =
ServerConfig(b.host, b.port, true)
extension (b: ServerConfig[HasHost, NeedsPort])
def withPort(p: Int): ServerConfig[HasHost, HasPort] =
ServerConfig(b.host, p, b.ssl)
// Only buildable when both host and port are set
extension (b: ServerConfig[HasHost, HasPort])
def build: String = s"Server ready at ${b.host}:${b.port} (ssl=${b.ssl})"
@main def run(): Unit =
// Door: must unlock before opening
val door = Door.locked("Front")
// door.open // Won't compile! Door is Locked
val unlocked = door.unlock
println(unlocked.open)
val relocked = unlocked.lock
// relocked.open // Won't compile again!
// Email: must verify before sending
val draft = Email.draft("alice@test.com", "Hello", "World")
// draft.send // Won't compile! Unverified
draft.verify match
case Right(verified) => println(verified.send)
case Left(err) => println(s"Error: $err")
// Invalid email
val bad = Email.draft("not-email", "", "body")
println(bad.verify) // Left
// Builder: must set host and port
val config = ServerConfig.builder
.withHost("0.0.0.0")
.withSSL
.withPort(443)
.build
println(config)
// This won't compile — port not set:
// ServerConfig.builder.withHost("localhost").buildUse Cases
- State machine enforcement at compile time
- Ensuring required operations before use
- Type-safe builder patterns
Tags
Related Snippets
Similar patterns you can reuse in the same workflow.
scalaadvanced
Opaque Types for Type Safety
Use Scala 3 opaque types to create zero-cost type wrappers for domain modeling.
Best for: Domain-driven type safety
#scala#opaque-types
scalaadvanced
Inline and Compile-Time Metaprogramming
Use Scala 3 inline, transparent inline, and compiletime for compile-time computation and code generation.
Best for: Compile-time computation
#scala#inline
scalaintermediate
Multiversal Equality in Scala 3
Use strict equality with CanEqual to prevent comparing unrelated types at compile time.
Best for: Preventing accidental type comparisons
#scala#equality
scalaintermediate
Type-Safe Identifiers Pattern
Create type-safe ID wrappers to prevent mixing different entity IDs at compile time.
Best for: Preventing entity ID mixups
#scala#type-safety