Consider the following type definition:
trait LiftF[F[_], G[_]] {
def liftF[A](fa: F[A]): G[A]
}
When providing a requirement for an implicit of this type in context bounds (using kind projector plugin) we have to write it like this:
def func[A, G[_], F[_]: LiftF[?[_], G]](a: F[A]): G[A]
I would like to get rid of the ?[_]
part, so my initial guess was to write a type To[G[_]]
that returns LiftF[?[_], G]
in order to transform the above function definition into
def func[A, G[_], F[_]: LiftF.To[G]](a: F[A]): G[A]
However, when writing type To
definition as
type To[G[_]] = LiftF[?[_], G]
I get the following compilation error:
Error:(17, 20) type Λ$ takes type parameters
type To[G[_]] = LiftF[?[_], G]
Trying to rewrite it with existential types produces the following type definition:
type To[G[_]] = LiftF[F, G] forSome { type F[X] }
This compiles fine but, unsuprisingly, can't be applied to other type parameters, so desired function definition can't be achieved.
I managed to implement the "partial application" part with code inspired by the aux pattern:
trait To[G[_]] {
type From[F[_]] = LiftF[F, G]
}
Sadly enough, this leaves me with syntax that is arguably worse than the original one:
def func[A, G[_], F[_]: LiftF.To[G]#From](a: F[A]): G[A]
My question is - can I achieve the originally proposed syntax in Scala with the help of kind projector or I should just stick with ?[_]
?
As far as I understand it, the kind-projector can't really help you here:
type To[G[_]] = LiftF[?[_], G]
would simply be mechanically rewritten into something like
type To[G[_]] = ({ type T[F[_]] = LiftF[F, G] })#T
but it's invalid in 2.12.x, because it expects a plain type of kind *
on the right hand side of the definition.
If you move the parameter F
to the left hand side, you end up with
type To[G[_], F[_]] = LiftF[F, G]
which you then have to use as To[G, ?[_]]
, which obviously doesn't buy you anything either, it simply swaps the order of arguments. Therefore I'd suggest to just use LiftF[?[_], G]
and take solace from the fact that you don't have to write out ({ type L[F[_]] = LiftF[F, G] })#L
explicitly.
By the way, in Dotty, this works just fine:
trait LiftF[F[_], G[_]] {
def liftF[A](fa: F[A]): G[A]
}
type To[G[_]] = [F[_]] => LiftF[F, G]
def f[A, G[_], F[_]: To[G]](a: F[A]): G[A] = implicitly[LiftF[F, G]].liftF(a)