Skip to content
Higher-Kinded Types
step 1/5

Reading — step 1 of 5

Learn

~1 min readType System Power

Most type parameters are types: class List[T]. Higher-kinded parameters are themselves generic — like a function over types.

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

The F[_] says: "F is itself a generic type, but I don't care which one yet." Option, List, Future all match.

Implement:

implicit val optionFunctor: Functor[Option] = new Functor[Option] {
    def map[A, B](fa: Option[A])(f: A => B): Option[B] = fa match {
        case Some(x) => Some(f(x))
        case None => None
    }
}

implicit val listFunctor: Functor[List] = new Functor[List] {
    def map[A, B](fa: List[A])(f: A => B): List[B] = fa.map(f)
}

Generic over the container:

def double[F[_]](container: F[Int])(implicit F: Functor[F]): F[Int] =
    F.map(container)(_ * 2)

double(Some(5))             // Some(10)
double(List(1, 2, 3))       // List(2, 4, 6)

One function, works on any "thing-with-a-Functor."

This is how Cats and Scalaz work — they define Functor, Applicative, Monad, Traverse, etc. as type classes parametrized over F[_]. Every concrete container provides instances; library code stays generic.

Kind notation:

  • * — a regular type (Int, String)
  • * -> * — a type that takes one type and produces a type (List, Option)
  • * -> * -> * — takes two type params (Map, Either)
  • (* -> *) -> * — takes a * -> * (like Functor[F[_]])

Most code never needs higher-kinded types. They're for libraries — abstraction over abstraction. When you see HKTs in real code, it's usually a Cats/Scalaz user.

Scala 3 simplified the syntax: Functor[F[_]] becomes Functor[F[X]] or stays the same. Mostly compatible.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…