В последнее время меня увлекают фишки с HKD.
Если ты делаешь простой интерпретатор чего-то типизированного, часто ты делаешь две отдельные ветки твоих выражений - типизированные и нетипизированные.
Нетипизированная форма удобная для чтения-сохранения, типизированная для интерпретации.
Между ними - тайпчек и стирание.
Т.к. они часто выглядят очень похоже, возникает желание запихнуть их в один тип.
Можно представить это как sealed trait Expr[A], типизированной формой будет Expr[Type], нетипизированной - Expr[Unit]
Далее иногда ты хочешь воспользоваться системой типов scala, для того, чтобы удобно работать с индексированными структурами и гарантировать, что тайпчекнутая форма не может иметь некорректного построения
Тогда ты можешь получить что-то вроде Expr[F[_], A], A - это собственно твой индекс, отвечающий соответствующему типу выражения, а F[_] - тайп-параметр, различающий типизированную и нетипизированную форму.
F[x] = Unit для нетипизированной и F[x] = x для типизированной.
Тогда к примеру case class Expr[F[_], A](term: Term[A], typ: F[Type[A]])
и Term - какой-то adt вроде имеющий например конструктор
case class App[F[_], A, B](lam : Expr[F, A => B], arg : Expr[F, A]) extends Term[F, B]
Ну и в общем трюк состоит в том, чтобы сделать Expr[+F[_], A]
, а
type Untyped[A] = Any
, type Typed[A] = A
Тогда оказывается, что Typed <: Untyped , и в то время, как при чтении ты всё ещё можешь заполнять места для Untyped[Type[A]] каким-то Unit
Но Expr[Typed, A] <: Expr[Untyped, A], т.е. стирание в обратную сторону - это просто забывание, что там что-то полезное стояло на местах с типами, и его можно делать без полного обхода.