Частично примененный тип лямбда в Scala с добрым проектором
Рассмотрим следующее определение типа:
trait LiftF[F[_], G[_]] {
def liftF[A](fa: F[A]): G[A]
}
При предоставлении требования для неявного этого типа в контексте контекста (используя добрый плагин проектора) мы должны написать это так:
def func[A, G[_], F[_]: LiftF[?[_], G]](a: F[A]): G[A]
Я хотел бы избавиться от ?[_]
часть, поэтому мое первоначальное предположение было написать тип To[G[_]]
это возвращает LiftF[?[_], G]
чтобы преобразовать приведенное выше определение функции в
def func[A, G[_], F[_]: LiftF.To[G]](a: F[A]): G[A]
Тем не менее, при написании типа To
определение как
type To[G[_]] = LiftF[?[_], G]
Я получаю следующую ошибку компиляции:
Error:(17, 20) type Λ$ takes type parameters
type To[G[_]] = LiftF[?[_], G]
Попытка переписать его с экзистенциальными типами приводит к следующему определению типа:
type To[G[_]] = LiftF[F, G] forSome { type F[X] }
Это прекрасно компилируется, но, что не удивительно, не может быть применено к другим параметрам типа, поэтому желаемое определение функции не может быть достигнуто.
Мне удалось реализовать часть "частичного приложения" с кодом, вдохновленным шаблоном aux:
trait To[G[_]] {
type From[F[_]] = LiftF[F, G]
}
К сожалению, это оставляет меня с синтаксисом, который, возможно, хуже, чем оригинальный:
def func[A, G[_], F[_]: LiftF.To[G]#From](a: F[A]): G[A]
У меня вопрос - могу ли я добиться изначально предложенного синтаксиса в Scala с помощью доброжелательного проектора, или я должен просто придерживаться ?[_]
?
1 ответ
Насколько я понимаю, добрый проектор не может вам здесь помочь:
type To[G[_]] = LiftF[?[_], G]
будет просто механически переписан в нечто вроде
type To[G[_]] = ({ type T[F[_]] = LiftF[F, G] })#T
но это неверно в 2.12.x, потому что он ожидает простой тип *
на правой стороне определения.
Если вы переместите параметр F
с левой стороны, вы в конечном итоге
type To[G[_], F[_]] = LiftF[F, G]
который вы затем должны использовать как To[G, ?[_]]
, который, очевидно, тоже ничего вам не покупает, он просто меняет порядок аргументов. Поэтому я бы предложил просто использовать LiftF[?[_], G]
и утешиться тем, что вам не нужно выписывать ({ type L[F[_]] = LiftF[F, G] })#L
в явном виде.
Кстати, в Dotty это работает просто отлично:
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)