Тип GADT как бесформенный продукт - как построить интерпретатор с произвольным количеством алгебр
Скажем, у меня два типа GADT.
abstract class Numbers[A]()
case class IntType() extends Numbers[Int]
abstract class Letters[A]()
case class EnglishType() extends Letters[String]
И у меня есть интерпретатор для каждого из типов GADT, который распечатает описание для каждого из подтипов GADT.
trait Interpreter[ALG[_],A] {
def description(a: ALG[A]) : String
}
case class NumbersInterpreter[A]() extends Interpreter[Numbers,A] {
override def description(a: Numbers[A]): String =
a match {
case i: IntType => "Int"
}
}
case class LettersInterpreter[A]() extends Interpreter[Letters,A] {
override def description(a: Letters[A]): String =
a match {
case e: EnglishType => "English"
}
}
Я хочу объединить два GADT в один GADT под названием All
type All[A] = Numbers[A] :+: Letters[A] :+: CNil
Я могу создать новый интерпретатор, жестко закодировав все значения GADT.
case class DualInterpreter[A](
numbersInterpreter: NumbersInterpreter[A],
lettersInterpreter: LettersInterpreter[A]) extends Interpreter[All,A] {
override def description(a: All[A]): String =
a match {
case Inl(num) => numbersInterpreter.description(num)
case Inr(Inl(let)) => lettersInterpreter.description(let)
case _ => sys.error("Unreachable Code")
}
}
Однако я хотел бы добавить кучу алгебр и интерпретаторов GADT и произвольно объединить их в единую алгебру, поэтому я ищу более общий подход для замены DualInterpreter
над. Я вижу, что подпись типа похожа на
case class ArbitraryInterpreter[ALG[_]<:Coproduct,A](???) extends Interpreter[ALG,A] {
override def description(a: ALG[A]): String = ???
}
Главное, что я хотел бы абстрагироваться, - это сопоставление шаблонов внутри description
метод, потому что он может стать довольно уродливым из-за количества доступных алгебр. У него будет интерпретатор, в котором аргументы конструктора являются интерпретаторами, а сопоставление шаблонов делегирует соответствующему интерпретатору на основе типа ALG, переданного в метод описания.
1 ответ
Вы имеете в виду что-то вроде этого?
// using kind projector
def merge[L[_], R[_] <: Coproduct, A](
li: Interpreter[L, A],
ri: Interpreter[R, A]
): Interpreter[Lambda[A => L[A] :+: R[A]] , A] =
new Interpreter[Lambda[A => L[A] :+: R[A]] , A] {
override def description(lr: L[A] :+: R[A]): String =
lr match {
case Inl(l) => li.description(l)
case Inr(r) => ri.description(r)
}
}
возможно с
implicit class InterpreterOps[L[_], A](val l: Interpreter[L, A]) extends AnyVal {
def ++ [R[_] <: Coproduct](r: Interpreter[R, A]): Interpreter[Lambda[A => L[A] :+: R[A]] , A] = merge(l, r)
}
используется как
type CNilF[A] = CNil // trick to make things consistent on kind-level
case class CNilInterpreter[A]() extends Interpreter[CNilF, A] {
override def description(a: CNilF[A]): String = ???
}
def allInterpreter[A]: Interpreter[All, A] =
// :+: is right associative, but normal methods are left associative,
// so we have to use parens
NumbersInterpreter[A]() ++ (LettersInterpreter[A]() ++ CNilInterpreter[A]())