Type-level computation lets libraries like typed SQL, JSON and data-frame APIs catch errors at compile time.
Scala 3 can compute types from types.
Match types are match expressions at the type level:
type Elem[X] = X match
case String => Char
case Array[t] => t
case Iterable[t] => tElem[List[Int]] reduces to Int. A method returning Elem[X] must be implemented with a matching term-level match so the compiler can relate the two.
Type lambdas [X] =>> Either[String, X] are anonymous type constructors, used when a type class expects a one-hole type like F[_]. Higher-kinded types (trait Functor[F[_]]) abstract over containers themselves; this is how Cats and ZIO define Functor and Monad.
Singleton and literal types treat values as types: 3, "id", true. With scala.compiletime.ops you can do arithmetic on them (type Five = 2 + 3), and constValue brings them back as values.
Tuples are generic heterogeneous lists in Scala 3 (*: and EmptyTuple), with type-level operations like Tuple.Concat, Tuple.Map and Tuple.Size; case classes convert to and from tuples via Mirror. This is the backbone of generic derivation.
Use these sparingly in application code: compile errors become hard to read and compile times grow. They shine in libraries where precise types prevent misuse.
Example
import scala.compiletime.ops.int.*
import scala.compiletime.constValue
type Elem[X] = X match
case String => Char
case Array[t] => t
case Iterable[t] => t
def first[X](x: X): Elem[X] = x match
case s: String => s.charAt(0)
case a: Array[t] => a(0)
case i: Iterable[t] => i.head
trait Functor[F[_]]:
extension [A](fa: F[A]) def fmap[B](f: A => B): F[B]
given Functor[[X] =>> Either[String, X]] with
extension [A](fa: Either[String, A]) def fmap[B](f: A => B) = fa.map(f)
@main def typeLevel(): Unit =
val c: Char = first("scala")
val n: Int = first(List(7, 8))
println((c, n)) // (s,7)
println(constValue[3 * 4]) // 12
val t = (1, "a", true)
val t2: (Int, String, Boolean, Double) = t ++ Tuple1(2.5)
println(t2) // (1,a,true,2.5)
val r: Either[String, Int] = Right(20)
println(r.fmap(_ + 1)) // Right(21)Remember
- Match types compute result types by matching on type arguments
- Type lambdas
[X] =>> F[X]fill one-hole type parameters - Literal types plus compiletime.ops do arithmetic at compile time
- Tuples are type-level lists that power derivation
Watch out for
- Writing a method with a match-type result but a body that doesn't mirror the type-level match, causing type errors.