Metaprogramming
transparent inline lets the result type be refined per call site.
32.1 inline
transparent inline lets the result type be refined per call site.
inline val Debug = false
inline def log(inline msg: String): Unit =
inline if Debug then println(msg) // removed entirely at compile time when Debug = false
inline def power(x: Double, inline n: Int): Double =
inline if n == 0 then 1.0
else if n % 2 == 1 then x * power(x, n - 1)
else { val y = power(x, n / 2); y * y } // fully unrolled at compile time32.2 compiletime utilities
import scala.compiletime.*
inline def typeName[T]: String = inline erasedValue[T] match
case _: Int => "Int"
case _: String => "String"
case _ => "Other"
inline def requirePositive(inline n: Int): Int =
inline if n <= 0 then error("must be positive") else n
summonInline[Ordering[Int]], constValue[3], codeOf(1 + 2)32.3 Macros with quotes & splices
'{ ... } quotes code into an Expr[T]; ${ ... } splices an Expr into code.
quotes.reflect gives full access to the typed AST (TASTy reflection).
Scala 3 macros are not compatible with Scala 2 macros (scala.reflect.macros).
// Macros.scala (must be compiled before use — separate file/module)
import scala.quoted.*
inline def debug(inline expr: Any): Unit = ${ debugImpl('expr) }
def debugImpl(expr: Expr[Any])(using Quotes): Expr[Unit] =
val code = expr.show
'{ println(${ Expr(code) } + " = " + $expr) }
// Usage
val x = 2
debug(x * 21) // prints: x.*(21) = 4232.4 Mirrors & type class derivation
Libraries: magnolia (derivation helper), shapeless 3, kittens.
import scala.deriving.Mirror
import scala.compiletime.{erasedValue, summonInline, constValueTuple}
trait Show[A]:
def show(a: A): String
object Show:
given Show[Int] = _.toString
given Show[String] = s => s"\"$s\""
inline def derived[A](using m: Mirror.ProductOf[A]): Show[A] =
val labels = constValueTuple[m.MirroredElemLabels].toList.map(_.toString)
val instances = summonAll[m.MirroredElemTypes]
(a: A) =>
val values = a.asInstanceOf[Product].productIterator.toList
val fields = labels.zip(values.zip(instances)).map { case (l, (v, s)) =>
s"$l = ${s.asInstanceOf[Show[Any]].show(v)}" }
fields.mkString(s"${constValue[m.MirroredLabel]}(", ", ", ")")
inline def summonAll[T <: Tuple]: List[Show[?]] = inline erasedValue[T] match
case _: EmptyTuple => Nil
case _: (t *: ts) => summonInline[Show[t]] :: summonAll[ts]
import scala.compiletime.constValue
case class Person(name: String, age: Int) derives Show
summon[Show[Person]].show(Person("Ann", 30)) // Person(name = "Ann", age = 30)32.5 Annotations
@main, @tailrec, @deprecated, @inline, @specialized (Scala 2), @targetName, @throws, @transient, @volatile, @unchecked, @nowarn, @experimental, @static, @publicInBinary.
32.6 Runtime reflection
Scala 3 has no runtime scala.reflect.runtime; use Java reflection or compile-time techniques. ClassTag and TypeTest remain available.