Reading — step 1 of 5
Learn
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* -> *(likeFunctor[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…