Type-Level Programming

(Scala 2 used the kind-projector plugin: Either[String, *].)

29.1 Higher-kinded types

trait Functor[F[_]]:
  extension [A](fa: F[A]) def map[B](f: A => B): F[B]

given Functor[List] with
  extension [A](fa: List[A]) def map[B](f: A => B) = fa.map(f)

given Functor[Option] with
  extension [A](fa: Option[A]) def map[B](f: A => B) = fa.map(f)

def double[F[_]: Functor](fa: F[Int]): F[Int] = fa.map(_ * 2)
double(List(1, 2)); double(Option(3))

29.2 Type lambdas

(Scala 2 used the kind-projector plugin: Either[String, *].)

type EitherString = [A] =>> Either[String, A]
given Functor[[A] =>> Either[String, A]] with
  extension [A](fa: Either[String, A]) def map[B](f: A => B) = fa.map(f)

29.3 Match types

type Elem[X] = X match
  case String => Char
  case Array[t] => t
  case Iterable[t] => t

val c: Elem[String] = 'a'
val i: Elem[List[Int]] = 1

type LeafElem[X] = X match
  case Iterable[t] => LeafElem[t]
  case _ => X

29.4 Dependent function types & polymorphic function types

trait Entry { type Key; val key: Key }
val extract: (e: Entry) => e.Key = e => e.key          // dependent function type

val identity: [A] => A => A = [A] => (a: A) => a       // polymorphic function value
val wrap: [A] => List[A] => Option[A] = [A] => (xs: List[A]) => xs.headOption

29.5 Tuples as HLists (Scala 3)

val t: (Int, String, Boolean) = 1 *: "a" *: true *: EmptyTuple
t.head; t.tail; t.size
t.map[[X] =>> Option[X]]([X] => (x: X) => Some(x))
type Concat = Tuple.Concat[(Int, String), (Boolean)]

29.6 compiletime ops

import scala.compiletime.ops.int.*
val five: 2 + 3 = 5
import scala.compiletime.{constValue, erasedValue, summonInline, error}
inline def sizeOf[T <: Tuple]: Int = constValue[Tuple.Size[T]]
sizeOf[(Int, String, Double)]   // 3

29.7 Type-level natural numbers (classic example)

sealed trait Nat
final class Zero extends Nat
final class Succ[N <: Nat] extends Nat

type Plus[A <: Nat, B <: Nat] <: Nat = A match
  case Zero => B
  case Succ[a] => Succ[Plus[a, B]]