Is there a reason why Scala cannot allow type parameter groups in the same way as regular parameter groups? For example:
def f[A, B][C](f (A, B) => C)
That way we could require some types and let the compiler to infer the others, e.g.:
f[Int, Int]: (a, b) =>
(a + b).toString
Currently the interleaved type parameters feature doesn’t allow that directly, because every type parameter list should be followed by regular parameters (in parentheses), but here the first parameter requires all the types at once. There is a work-around though:
def f[A, B]()[C](f (A, B) => C) = ???
f[Int, Int](): (a, b) =>
(a + b).toString
But it doesn’t look very neat.
Nevertheless, the work-around shows that it should be feasible in principle.
If there were paramter groups in Scala, a “partial apply” pattern (which is quite ubiqutous, I’d say) could look way nicer. For example, currently in cats:
final class OptionOps[A](private val oa: Option[A]) extends AnyVal {
def liftTo[F[_]]: LiftToPartiallyApplied[F, A] = new LiftToPartiallyApplied(oa)
}
object OptionOps {
final class LiftToPartiallyApplied[F[_], A](oa: Option[A]):
def apply[E](ifEmpty: => E)(implicit F: ApplicativeError[F, ? >: E]): F[A] =
ApplicativeError.liftFromOption(oa, ifEmpty)
}
All that hassle is only required to support this syntax:
val opt: Option[String] = ...
opt.liftTo[IO]:
new Exception("OOPS")
That could be simplified substantially with parameter groups, I guess:
// We could also benefit from `extension` here but it is out of the scope...
final class OptionOps[A](private val oa: Option[A]) extends AnyVal:
def liftTo[F[_]][E](ifEmpty: => E)(using F: ApplicativeError[F, ? >: E]): F[A] =
ApplicativeError.liftFromOption(oa, ifEmpty)
Then in opt.liftTo[IO] we define the first type, but let the compiler to infer the rest.
Moreover, it would presumably unify type parameters and regular parameters to some extent.