Scala 3 Advanced Features: Union Types, Intersection Types, and Opaque Types
Scala 3 introduces revolutionary type system enhancements that provide unprecedented expressiveness and safety. This comprehensive lesson explores union types, intersection types, opaque types, and other advanced features that make Scala 3 a cutting-edge programming language for building robust, type-safe applications.
Union Types: Expressing Alternatives
Basic Union Type Concepts
// Union types represent values that can be one of several types
type StringOrInt = String | Int
type NumberOrBoolean = Int | Double | Boolean
type Result[T] = T | Error
case class Error(message: String, code: Int)
// Function using union types
def processValue(value: StringOrInt): String = value match {
case s: String => s"String: $s"
case i: Int => s"Integer: $i"
}
// Multiple alternatives
def formatNumber(value: NumberOrBoolean): String = value match {
case i: Int => f"Integer: $i%d"
case d: Double => f"Double: $d%.2f"
case b: Boolean => s"Boolean: $b"
}
// Result type with error handling
def divideNumbers(a: Double, b: Double): Result[Double] = {
if (b == 0) Error("Division by zero", 400)
else a / b
}
// Working with result types
def handleResult[T](result: Result[T]): String = result match {
case error: Error => s"Error ${error.code}: ${error.message}"
case value: T => s"Success: $value"
}
// Example usage
val stringValue: StringOrInt = "Hello"
val intValue: StringOrInt = 42
println(processValue(stringValue)) // String: Hello
println(processValue(intValue)) // Integer: 42
val divisionResult = divideNumbers(10.0, 2.0)
println(handleResult(divisionResult)) // Success: 5.0
val errorResult = divideNumbers(10.0, 0.0)
println(handleResult(errorResult)) // Error 400: Division by zero
Advanced Union Type Patterns
// Complex union types for domain modeling
object DomainModeling {
// Payment method union type
sealed trait PaymentMethod
case class CreditCard(number: String, expiryMonth: Int, expiryYear: Int, cvv: String) extends PaymentMethod
case class PayPal(email: String) extends PaymentMethod
case class BankTransfer(accountNumber: String, routingNumber: String) extends PaymentMethod
case class Cryptocurrency(address: String, currency: String) extends PaymentMethod
type PaymentOrError = PaymentMethod | PaymentError
sealed trait PaymentError
case class InvalidCardNumber(providedNumber: String) extends PaymentError
case class ExpiredCard(expiryMonth: Int, expiryYear: Int) extends PaymentError
case class InvalidEmail(email: String) extends PaymentError
case class InsufficientFunds(available: BigDecimal, required: BigDecimal) extends PaymentError
// Validation using union types
def validatePaymentMethod(input: String, methodType: String): PaymentOrError = {
methodType.toLowerCase match {
case "creditcard" =>
if (input.matches("\\d{16}")) {
CreditCard(input, 12, 2025, "123")
} else {
InvalidCardNumber(input)
}
case "paypal" =>
if (input.contains("@") && input.contains(".")) {
PayPal(input)
} else {
InvalidEmail(input)
}
case "crypto" =>
if (input.length >= 26 && input.length <= 35) {
Cryptocurrency(input, "BTC")
} else {
InvalidCardNumber(input) // Reusing error type
}
case _ =>
InvalidEmail("Unknown payment method")
}
}
// Processing payment with union types
def processPayment(method: PaymentOrError, amount: BigDecimal): String = method match {
case card: CreditCard =>
s"Processing $$${amount} via credit card ending in ${card.number.takeRight(4)}"
case paypal: PayPal =>
s"Processing $$${amount} via PayPal account ${paypal.email}"
case bank: BankTransfer =>
s"Processing $$${amount} via bank transfer to ${bank.accountNumber}"
case crypto: Cryptocurrency =>
s"Processing $$${amount} via ${crypto.currency} to address ${crypto.address.take(8)}..."
case error: PaymentError =>
s"Payment failed: ${formatPaymentError(error)}"
}
private def formatPaymentError(error: PaymentError): String = error match {
case InvalidCardNumber(number) => s"Invalid card number: $number"
case ExpiredCard(month, year) => s"Card expired: $month/$year"
case InvalidEmail(email) => s"Invalid email: $email"
case InsufficientFunds(available, required) =>
s"Insufficient funds: $$${available} available, $$${required} required"
}
// Union types with generic parameters
type ApiResponse[T] = T | ApiError
sealed trait ApiError
case class NetworkError(message: String, retryAfter: Option[Int] = None) extends ApiError
case class ValidationError(field: String, message: String) extends ApiError
case class AuthenticationError(message: String) extends ApiError
case class ServerError(code: Int, message: String) extends ApiError
// API client using union types
class ApiClient {
def fetchUser(id: String): ApiResponse[User] = {
if (id.nonEmpty && id.forall(_.isDigit)) {
User(id, s"User$id", s"user$id@example.com")
} else {
ValidationError("id", "User ID must be numeric")
}
}
def updateUser(user: User): ApiResponse[User] = {
if (user.email.contains("@")) {
user.copy(name = user.name.toUpperCase)
} else {
ValidationError("email", "Invalid email format")
}
}
def deleteUser(id: String): ApiResponse[Unit] = {
if (id == "admin") {
AuthenticationError("Cannot delete admin user")
} else {
()
}
}
}
case class User(id: String, name: String, email: String)
// Helper functions for working with API responses
extension [T](response: ApiResponse[T]) {
def map[U](f: T => U): ApiResponse[U] = response match {
case value: T => f(value)
case error: ApiError => error
}
def flatMap[U](f: T => ApiResponse[U]): ApiResponse[U] = response match {
case value: T => f(value)
case error: ApiError => error
}
def fold[U](onError: ApiError => U, onSuccess: T => U): U = response match {
case error: ApiError => onError(error)
case value: T => onSuccess(value)
}
def getOrElse(default: T): T = response match {
case value: T => value
case _: ApiError => default
}
def isSuccess: Boolean = response match {
case _: T => true
case _: ApiError => false
}
def isError: Boolean = !isSuccess
}
}
Intersection Types: Combining Capabilities
Basic Intersection Type Usage
// Intersection types combine multiple types
trait Readable {
def read(): String
}
trait Writable {
def write(content: String): Unit
}
trait Seekable {
def seek(position: Long): Unit
def position: Long
}
// Intersection type combining multiple traits
type ReadWrite = Readable & Writable
type FullAccess = Readable & Writable & Seekable
// Implementation of intersection types
class FileHandler extends Readable, Writable, Seekable {
private var content = ""
private var pos = 0L
def read(): String = content
def write(content: String): Unit = { this.content = content }
def seek(position: Long): Unit = { this.pos = position }
def position: Long = pos
}
// Function requiring intersection type
def processFile(file: ReadWrite): String = {
val content = file.read()
file.write(content.toUpperCase)
file.read()
}
// Advanced intersection with generic types
trait Serializable[T] {
def serialize(value: T): String
def deserialize(data: String): T
}
trait Validatable[T] {
def validate(value: T): Boolean
def getValidationErrors(value: T): List[String]
}
trait Cacheable[T] {
def cacheKey(value: T): String
def cacheTTL: Int
}
// Complex intersection type
type DataProcessor[T] = Serializable[T] & Validatable[T] & Cacheable[T]
// Implementation
class UserProcessor extends Serializable[User], Validatable[User], Cacheable[User] {
def serialize(user: User): String =
s"${user.id}|${user.name}|${user.email}"
def deserialize(data: String): User = {
val parts = data.split("\\|")
User(parts(0), parts(1), parts(2))
}
def validate(user: User): Boolean =
user.name.nonEmpty && user.email.contains("@")
def getValidationErrors(user: User): List[String] = {
var errors = List.empty[String]
if (user.name.isEmpty) errors = "Name cannot be empty" :: errors
if (!user.email.contains("@")) errors = "Invalid email format" :: errors
errors
}
def cacheKey(user: User): String = s"user:${user.id}"
def cacheTTL: Int = 300 // 5 minutes
}
// Using intersection types in generic contexts
def processData[T](data: T, processor: DataProcessor[T]): Either[List[String], String] = {
if (processor.validate(data)) {
Right(processor.serialize(data))
} else {
Left(processor.getValidationErrors(data))
}
}
Structural Types and Intersection
// Structural types with intersection
object StructuralIntersection {
// Duck typing with intersection
type Drawable = { def draw(): Unit }
type Movable = { def move(x: Int, y: Int): Unit }
type Interactive = Drawable & Movable
class Circle(var x: Int, var y: Int, val radius: Int) {
def draw(): Unit = println(s"Drawing circle at ($x, $y) with radius $radius")
def move(newX: Int, newY: Int): Unit = { x = newX; y = newY }
}
class Rectangle(var x: Int, var y: Int, val width: Int, val height: Int) {
def draw(): Unit = println(s"Drawing rectangle at ($x, $y) ${width}x${height}")
def move(newX: Int, newY: Int): Unit = { x = newX; y = newY }
}
// Function working with intersection type
def manipulateShape(shape: Interactive): Unit = {
shape.draw()
shape.move(10, 20)
shape.draw()
}
// Advanced structural intersection
type Configurable = {
def configure(config: Map[String, Any]): Unit
def getConfig(): Map[String, Any]
}
type Monitorable = {
def getMetrics(): Map[String, Double]
def healthCheck(): Boolean
}
type ManagedComponent = Configurable & Monitorable
class DatabaseConnection extends {
private var config = Map.empty[String, Any]
private var connectionCount = 0.0
private var queryTime = 0.0
def configure(newConfig: Map[String, Any]): Unit = {
config = newConfig
}
def getConfig(): Map[String, Any] = config
def getMetrics(): Map[String, Double] = Map(
"connections" -> connectionCount,
"avgQueryTime" -> queryTime
)
def healthCheck(): Boolean = true
def executeQuery(sql: String): Unit = {
connectionCount += 1
queryTime = scala.util.Random.nextDouble() * 100
}
}
// Management function
def manageComponent(component: ManagedComponent): Unit = {
// Configure
component.configure(Map("timeout" -> 30, "maxConnections" -> 100))
// Monitor
if (component.healthCheck()) {
val metrics = component.getMetrics()
println(s"Component healthy. Metrics: $metrics")
} else {
println("Component unhealthy!")
}
}
}
Opaque Types: Type-Safe Abstractions
Basic Opaque Type Definitions
// Opaque types provide zero-cost abstractions
object OpaqueTypeBasics {
// Basic opaque type
opaque type UserId = String
object UserId {
def apply(value: String): UserId = value
def fromString(value: String): Option[UserId] =
if (value.nonEmpty && value.forall(_.isLetterOrDigit)) Some(value) else None
}
extension (userId: UserId) {
def value: String = userId
def isAdmin: Boolean = userId.startsWith("admin")
}
// Multiple opaque types for type safety
opaque type EmailAddress = String
opaque type PhoneNumber = String
opaque type OrderId = Long
opaque type ProductId = String
object EmailAddress {
def apply(email: String): EmailAddress = email
def fromString(email: String): Option[EmailAddress] =
if (email.contains("@") && email.contains(".")) Some(email) else None
}
object PhoneNumber {
def apply(phone: String): PhoneNumber = phone
def fromString(phone: String): Option[PhoneNumber] =
if (phone.matches("\\+?\\d{10,15}")) Some(phone) else None
}
object OrderId {
def apply(id: Long): OrderId = id
def generate(): OrderId = System.currentTimeMillis()
}
object ProductId {
def apply(id: String): ProductId = id
def fromSKU(sku: String): ProductId = s"prod_$sku"
}
// Extension methods for opaque types
extension (email: EmailAddress) {
def domain: String = email.split("@").last
def localPart: String = email.split("@").head
def isGmail: Boolean = domain == "gmail.com"
}
extension (phone: PhoneNumber) {
def formatted: String = {
val digits = phone.replaceAll("\\D", "")
if (digits.length == 10) {
s"(${digits.take(3)}) ${digits.slice(3, 6)}-${digits.drop(6)}"
} else {
phone
}
}
def countryCode: Option[String] =
if (phone.startsWith("+")) Some(phone.takeWhile(_ != ' ').drop(1)) else None
}
extension (orderId: OrderId) {
def timestamp: Long = orderId
def isRecent: Boolean = System.currentTimeMillis() - orderId < 24 * 60 * 60 * 1000 // 24 hours
}
extension (productId: ProductId) {
def category: String = productId.split("_").headOption.getOrElse("unknown")
def sku: String = productId.split("_").drop(1).mkString("_")
}
// Domain model using opaque types
case class User(
id: UserId,
email: EmailAddress,
phone: Option[PhoneNumber]
)
case class Order(
id: OrderId,
userId: UserId,
products: List[ProductId],
total: BigDecimal
)
// Business logic with type safety
def createUser(
idStr: String,
emailStr: String,
phoneStr: Option[String]
): Either[String, User] = {
for {
userId <- UserId.fromString(idStr).toRight("Invalid user ID")
email <- EmailAddress.fromString(emailStr).toRight("Invalid email")
phone <- phoneStr.map(PhoneNumber.fromString).sequence.toRight("Invalid phone number")
} yield User(userId, email, phone)
}
def createOrder(user: User, productIds: List[String]): Order = {
Order(
id = OrderId.generate(),
userId = user.id,
products = productIds.map(ProductId.apply),
total = BigDecimal(99.99)
)
}
// Helper extension for Option sequence
extension [T](option: Option[Option[T]]) {
def sequence: Option[Option[T]] = option match {
case Some(Some(value)) => Some(Some(value))
case Some(None) => None
case None => Some(None)
}
}
}
Advanced Opaque Type Patterns
// Complex opaque types with validation and operations
object AdvancedOpaqueTypes {
// Financial types with precision
opaque type Money = BigDecimal
opaque type Currency = String
opaque type ExchangeRate = BigDecimal
object Money {
def apply(amount: BigDecimal): Money = amount.setScale(2, BigDecimal.RoundingMode.HALF_UP)
def apply(amount: Double): Money = Money(BigDecimal(amount))
def zero: Money = Money(BigDecimal(0))
def fromString(str: String): Option[Money] = {
try {
val amount = BigDecimal(str)
if (amount >= 0) Some(Money(amount)) else None
} catch {
case _: NumberFormatException => None
}
}
}
object Currency {
val USD: Currency = "USD"
val EUR: Currency = "EUR"
val GBP: Currency = "GBP"
val JPY: Currency = "JPY"
def apply(code: String): Option[Currency] = {
val validCurrencies = Set("USD", "EUR", "GBP", "JPY", "CAD", "AUD", "CHF")
if (validCurrencies.contains(code.toUpperCase)) Some(code.toUpperCase) else None
}
}
object ExchangeRate {
def apply(rate: BigDecimal): ExchangeRate = rate.setScale(6, BigDecimal.RoundingMode.HALF_UP)
def apply(rate: Double): ExchangeRate = ExchangeRate(BigDecimal(rate))
}
// Extensions for money operations
extension (money: Money) {
def value: BigDecimal = money
def cents: Long = (money * 100).toLong
def +(other: Money): Money = Money(money + other)
def -(other: Money): Money = Money(money - other)
def *(multiplier: BigDecimal): Money = Money(money * multiplier)
def /(divisor: BigDecimal): Money = Money(money / divisor)
def format(currency: Currency): String = {
val symbol = currency match {
case "USD" => "$"
case "EUR" => "€"
case "GBP" => "£"
case "JPY" => "¥"
case _ => currency + " "
}
s"$symbol${money.setScale(2)}"
}
def convert(rate: ExchangeRate): Money = Money(money * rate)
def isPositive: Boolean = money > 0
def isZero: Boolean = money == 0
def isNegative: Boolean = money < 0
}
extension (currency: Currency) {
def code: String = currency
def symbol: String = currency match {
case "USD" => "$"
case "EUR" => "€"
case "GBP" => "£"
case "JPY" => "¥"
case _ => currency
}
}
extension (rate: ExchangeRate) {
def value: BigDecimal = rate
def inverse: ExchangeRate = ExchangeRate(BigDecimal(1) / rate)
}
// Money amount with currency
case class CurrencyAmount(amount: Money, currency: Currency) {
def format: String = amount.format(currency)
def convert(toCurrency: Currency, rate: ExchangeRate): CurrencyAmount =
CurrencyAmount(amount.convert(rate), toCurrency)
}
// Coordinates with type safety
opaque type Latitude = Double
opaque type Longitude = Double
opaque type Altitude = Double
object Latitude {
def apply(value: Double): Option[Latitude] =
if (value >= -90.0 && value <= 90.0) Some(value) else None
def fromDegrees(degrees: Int, minutes: Int, seconds: Double, hemisphere: Char): Option[Latitude] = {
val decimal = degrees + minutes / 60.0 + seconds / 3600.0
val signed = if (hemisphere.toUpper == 'S') -decimal else decimal
Latitude(signed)
}
}
object Longitude {
def apply(value: Double): Option[Longitude] =
if (value >= -180.0 && value <= 180.0) Some(value) else None
def fromDegrees(degrees: Int, minutes: Int, seconds: Double, hemisphere: Char): Option[Longitude] = {
val decimal = degrees + minutes / 60.0 + seconds / 3600.0
val signed = if (hemisphere.toUpper == 'W') -decimal else decimal
Longitude(signed)
}
}
object Altitude {
def apply(value: Double): Altitude = value
def seaLevel: Altitude = 0.0
def fromFeet(feet: Double): Altitude = feet * 0.3048
}
extension (lat: Latitude) {
def value: Double = lat
def degrees: Int = lat.toInt
def minutes: Int = ((lat.abs % 1) * 60).toInt
def seconds: Double = (((lat.abs % 1) * 60) % 1) * 60
def hemisphere: Char = if (lat >= 0) 'N' else 'S'
def toDMS: String = s"${degrees.abs}°${minutes}'${seconds}\"${hemisphere}"
}
extension (lon: Longitude) {
def value: Double = lon
def degrees: Int = lon.toInt
def minutes: Int = ((lon.abs % 1) * 60).toInt
def seconds: Double = (((lon.abs % 1) * 60) % 1) * 60
def hemisphere: Char = if (lon >= 0) 'E' else 'W'
def toDMS: String = s"${degrees.abs}°${minutes}'${seconds}\"${hemisphere}"
}
extension (alt: Altitude) {
def value: Double = alt
def meters: Double = alt
def feet: Double = alt / 0.3048
def isAboveSeaLevel: Boolean = alt > 0
def isBelowSeaLevel: Boolean = alt < 0
}
// Geographic coordinate system
case class Coordinates(
latitude: Latitude,
longitude: Longitude,
altitude: Option[Altitude] = None
) {
def distanceTo(other: Coordinates): Double = {
// Haversine formula for great circle distance
val R = 6371000 // Earth's radius in meters
val lat1Rad = math.toRadians(latitude.value)
val lat2Rad = math.toRadians(other.latitude.value)
val deltaLatRad = math.toRadians(other.latitude.value - latitude.value)
val deltaLonRad = math.toRadians(other.longitude.value - longitude.value)
val a = math.sin(deltaLatRad / 2) * math.sin(deltaLatRad / 2) +
math.cos(lat1Rad) * math.cos(lat2Rad) *
math.sin(deltaLonRad / 2) * math.sin(deltaLonRad / 2)
val c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
R * c
}
def format: String = {
val altStr = altitude.map(alt => s", ${alt.meters}m").getOrElse("")
s"${latitude.toDMS}, ${longitude.toDMS}$altStr"
}
}
// Time types with precision
opaque type Timestamp = Long
opaque type Duration = Long
opaque type TimeZoneOffset = Int
object Timestamp {
def now(): Timestamp = System.currentTimeMillis()
def apply(millis: Long): Timestamp = millis
def fromInstant(instant: java.time.Instant): Timestamp = instant.toEpochMilli
}
object Duration {
def apply(millis: Long): Duration = millis
def seconds(seconds: Long): Duration = seconds * 1000
def minutes(minutes: Long): Duration = minutes * 60 * 1000
def hours(hours: Long): Duration = hours * 60 * 60 * 1000
def days(days: Long): Duration = days * 24 * 60 * 60 * 1000
}
object TimeZoneOffset {
def apply(offsetMinutes: Int): TimeZoneOffset = offsetMinutes
def UTC: TimeZoneOffset = 0
def EST: TimeZoneOffset = -5 * 60
def PST: TimeZoneOffset = -8 * 60
}
extension (timestamp: Timestamp) {
def value: Long = timestamp
def +(duration: Duration): Timestamp = Timestamp(timestamp + duration)
def -(duration: Duration): Timestamp = Timestamp(timestamp - duration)
def -(other: Timestamp): Duration = Duration(timestamp - other)
def toInstant: java.time.Instant = java.time.Instant.ofEpochMilli(timestamp)
def format: String = toInstant.toString
}
extension (duration: Duration) {
def value: Long = duration
def +(other: Duration): Duration = Duration(duration + other)
def -(other: Duration): Duration = Duration(duration - other)
def *(multiplier: Int): Duration = Duration(duration * multiplier)
def /(divisor: Int): Duration = Duration(duration / divisor)
def toSeconds: Long = duration / 1000
def toMinutes: Long = duration / (60 * 1000)
def toHours: Long = duration / (60 * 60 * 1000)
def toDays: Long = duration / (24 * 60 * 60 * 1000)
def format: String = {
val days = toDays
val hours = (toHours % 24)
val minutes = (toMinutes % 60)
val seconds = (toSeconds % 60)
if (days > 0) s"${days}d ${hours}h ${minutes}m ${seconds}s"
else if (hours > 0) s"${hours}h ${minutes}m ${seconds}s"
else if (minutes > 0) s"${minutes}m ${seconds}s"
else s"${seconds}s"
}
}
extension (offset: TimeZoneOffset) {
def value: Int = offset
def toHours: Int = offset / 60
def toMinutes: Int = offset % 60
def format: String = {
val hours = toHours
val minutes = toMinutes.abs
val sign = if (offset >= 0) "+" else "-"
f"$sign$hours%02d:$minutes%02d"
}
}
}
Inline Definitions and Transparent Types
Inline Functions and Values
// Inline definitions for compile-time optimization
object InlineDefinitions {
// Inline values - computed at compile time
inline val MAX_RETRIES = 3
inline val DEFAULT_TIMEOUT = 30000L
inline val PI_PRECISE = 3.141592653589793
// Inline functions - expanded at call site
inline def square(x: Double): Double = x * x
inline def cube(x: Double): Double = x * x * x
inline def max(a: Int, b: Int): Int = if (a > b) a else b
inline def min(a: Int, b: Int): Int = if (a < b) a else b
// Inline functions with type parameters
inline def identity[T](x: T): T = x
inline def const[T, U](x: T)(y: U): T = x
inline def swap[T, U](pair: (T, U)): (U, T) = (pair._2, pair._1)
// Complex inline computations
inline def factorial(n: Int): Long = {
if (n <= 1) 1L
else n * factorial(n - 1)
}
inline def fibonacci(n: Int): Long = {
if (n <= 1) n.toLong
else fibonacci(n - 1) + fibonacci(n - 2)
}
// Inline conditional compilation
inline def debugPrint(message: String): Unit = {
inline if (scala.util.Properties.propOrElse("debug", "false") == "true") {
println(s"DEBUG: $message")
}
}
inline def assertDebug(condition: Boolean, message: String): Unit = {
inline if (scala.util.Properties.propOrElse("debug", "false") == "true") {
assert(condition, message)
}
}
// Inline pattern matching
inline def processValue(value: Any): String = value match {
case i: Int => s"Integer: $i"
case s: String => s"String: $s"
case d: Double => s"Double: $d"
case _ => "Unknown type"
}
// Inline with dependent types
inline def selectType[T](inline evidence: Boolean): Any = {
inline if (evidence) {
null.asInstanceOf[T]
} else {
throw new RuntimeException("Type not available")
}
}
// Performance-critical inline functions
inline def fastModPowerOfTwo(value: Int, mod: Int): Int = {
// Only works when mod is power of 2
value & (mod - 1)
}
inline def isPowerOfTwo(n: Int): Boolean = {
n > 0 && (n & (n - 1)) == 0
}
inline def nextPowerOfTwo(n: Int): Int = {
if (n <= 1) 1
else {
var result = 1
while (result < n) result <<= 1
result
}
}
// Inline builders for DSLs
inline def html(inline content: String): String = {
s"<html><body>$content</body></html>"
}
inline def div(inline className: String, inline content: String): String = {
s"""<div class="$className">$content</div>"""
}
inline def span(inline content: String): String = {
s"<span>$content</span>"
}
// Usage examples
val result1 = square(5.0) // Inlined to: 5.0 * 5.0
val result2 = max(10, 20) // Inlined to: if (10 > 20) 10 else 20
val fact5 = factorial(5) // Computed at compile time: 120L
debugPrint("This will only print if debug=true")
val htmlDoc = html(
div("container",
span("Hello, World!")
)
)
}
// Transparent inline for type-level programming
object TransparentInline {
// Transparent inline functions preserve exact types
transparent inline def selectFirst[T, U](x: T, y: U): T | U = x
transparent inline def selectSecond[T, U](x: T, y: U): T | U = y
transparent inline def chooseType[T, U](inline condition: Boolean): Any =
inline if (condition) null.asInstanceOf[T] else null.asInstanceOf[U]
// Type-level computations
transparent inline def sizeOf[T]: Int = {
inline scala.compiletime.erasedValue[T] match {
case _: Byte => 1
case _: Short => 2
case _: Int => 4
case _: Long => 8
case _: Float => 4
case _: Double => 8
case _: Boolean => 1
case _: Char => 2
case _ => -1 // Unknown size
}
}
// Compile-time string manipulation
import scala.compiletime.{constValue, constValueTuple}
transparent inline def stringLength[S <: String & Singleton]: Int = {
constValue[S].length
}
transparent inline def stringConcat[S1 <: String & Singleton, S2 <: String & Singleton]: String = {
constValue[S1] + constValue[S2]
}
// Type-level list operations
transparent inline def tupleHead[H, T <: Tuple]: H = {
inline scala.compiletime.erasedValue[H *: T] match {
case _: (h *: t) => null.asInstanceOf[H]
}
}
transparent inline def tupleTail[H, T <: Tuple]: T = {
null.asInstanceOf[T]
}
// Compile-time validation
inline def validateRange(inline value: Int, inline min: Int, inline max: Int): Int = {
inline if (value < min || value > max) {
scala.compiletime.error(s"Value $value is outside range [$min, $max]")
}
value
}
inline def validateNonEmpty[S <: String & Singleton]: S = {
inline if (constValue[S].isEmpty) {
scala.compiletime.error("String cannot be empty")
}
constValue[S]
}
// Usage examples
val intSize = sizeOf[Int] // 4
val doubleSize = sizeOf[Double] // 8
val len = stringLength["Hello"] // 5
val combined = stringConcat["Hello", "World"] // "HelloWorld"
val validValue = validateRange(15, 10, 20) // OK
// val invalidValue = validateRange(25, 10, 20) // Compile error
val validString = validateNonEmpty["Hello"] // OK
// val invalidString = validateNonEmpty[""] // Compile error
}
Conclusion
Scala 3's advanced type system features provide unprecedented expressiveness and safety for building robust applications. Key concepts include:
Union Types:
- Express alternative types naturally with
|
syntax - Eliminate complex Either chains and option types
- Model domain errors and success states cleanly
- Enable functional error handling patterns
Intersection Types:
- Combine multiple capabilities with
&
syntax - Create powerful abstractions through composition
- Support structural typing for duck typing patterns
- Enable mixin-style programming with type safety
Opaque Types:
- Create zero-cost type abstractions
- Provide compile-time safety without runtime overhead
- Enable domain-specific type systems
- Support gradual type refinement and validation
Inline Definitions:
- Optimize performance through compile-time expansion
- Enable metaprogramming and code generation
- Support conditional compilation and feature flags
- Create efficient domain-specific languages
Advanced Patterns:
- Combine features for sophisticated type modeling
- Create type-safe APIs with minimal boilerplate
- Enable compile-time validation and optimization
- Support gradual migration from Scala 2
Best Practices:
- Use union types for error handling and alternatives
- Apply intersection types for capability composition
- Leverage opaque types for domain modeling
- Employ inline definitions for performance-critical code
- Combine features thoughtfully to avoid complexity
Migration Strategies:
- Introduce opaque types to replace type aliases
- Convert Either chains to union types gradually
- Use intersection types instead of cake pattern
- Apply inline definitions to hot code paths
Performance Considerations:
- Union types have minimal runtime overhead
- Intersection types enable efficient capability stacking
- Opaque types provide zero-cost abstractions
- Inline definitions eliminate function call overhead
Scala 3's type system advances represent a significant evolution in programming language design, offering developers powerful tools for expressing complex domain models while maintaining excellent performance characteristics and compile-time safety guarantees.
Comments
Be the first to comment on this lesson!