Teaching ChatGPT About Higher-Kinded Types
One of my favourite interview questions is to implement this function:
def repack[A](xs: Seq[Future[A]]): Future[(Seq[A], Seq[Throwable])] = ???
It separates successes from failures across a sequence of futures and collects both. Not particularly difficult, and ChatGPT handles it without trouble.
The more interesting question is the generalized form:
def repack[F[_], A](xs: Seq[F[A]]): F[(Seq[A], Seq[Throwable])] = ???
Replace Future with an abstract effect type F[_]. What constraints does F need?
ChatGPT’s answer:
To implement the function repack with the signature
def repack[F[_], A](xs: Seq[F[A]]): F[(Seq[A], Seq[Throwable])],
you need to put constraints on F[_] that provide the necessary
functionality to work with F as a context for asynchronous computations.
Here are the constraints you need:
F[_] must be a type constructor:
This means that F must be a higher-kinded type,
which allows it to take another type (e.g., A)
and produce a concrete type (e.g., F[A]).
This constraint enables you to work with
generic effect types like Future, IO, Task, etc.
F[_] must have an instance of MonadError:
MonadError provides the ability to handle errors within the context of F.
It includes operations like flatMap for sequencing computations and raiseError
for lifting exceptions into the F context. This constraint allows you to handle
both successful results and failures within F.
And the implementation:
import cats.MonadError
import cats.implicits._
def repack[F[_]: MonadError[*[_], Throwable], A](xs: Seq[F[A]]): F[(Seq[A], Seq[Throwable])] =
xs.foldLeft(MonadError[F, Throwable].pure((Seq.empty[A], Seq.empty[Throwable]))) { (acc, next) =>
acc.flatMap { case (successes, failures) =>
next.attempt.map {
case Right(value) => (successes :+ value, failures)
case Left(exception) => (successes, failures :+ exception)
}
}
}
The reasoning is correct: MonadError[F, Throwable] provides pure for lifting values, flatMap for sequencing, and attempt for converting a potential failure into an Either. That is exactly the minimal constraint needed.
What’s notable is that ChatGPT not only produced working code but correctly identified why each constraint is required — which is the harder part of the question.