Всякий раз, когда мне приходилось создавать AST в Scala, я использовал шаблон абстрактного запечатанного типажа/кейса. До сих пор это работало очень хорошо, проверка соответствия шаблонов компилятором - большая победа.
Однако теперь я столкнулся с проблемой, с которой не могу справиться. Что делать, если у меня есть 2 языка, где один является подмножеством другого? В качестве простого примера рассмотрим лямбда-исчисление, в котором каждая переменная связана, и другой родственный язык, в котором переменные могут быть связаны или свободны.
Первый язык:
abstract sealed class Expression
case class Variable(val scope: Lambda, val name:String) extends Expression
case class Lambda(val v: Variable, val inner: Expression) extends Expression
case class Application(val function: Expression, val input: Expression) extends Expression
Второй язык:
abstract sealed class Expression
case class Variable(val name:String) extends Expression
case class Lambda(val v: Variable, val inner: Expression) extends Expression
case class Application(val function: Expression, val input: Expression) extends Expression
Где единственным изменением является удаление области действия из Variable.
Как видите, избыточности много. Но поскольку я использую запечатанные классы, трудно придумать хороший способ расширить его. Еще одна проблема их объединения заключается в том, что теперь каждая лямбда и приложение должны отслеживать язык своих параметров на уровне типа.
Этот пример не так уж плох, потому что он очень маленький, но представьте ту же проблему для строгого HTML/слабого HTML.