每当我必须在Scala中创建AST时,我就使用了抽象的密封特征/案例类模式。它到目前为止工作得很好,编译器检查模式匹配是一个很大的胜利。
然而现在我遇到了一个我无法解决的问题。如果我有2种语言,其中一种是另一种语言的子集怎么办?作为一个简单的例子,考虑一个lambda演算,其中每个变量都被绑定,另一个相关的语言可以绑定或释放变量。
第一语言:
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
唯一的变化是从变量中删除范围。
正如您所看到的,存在大量冗余。但是因为我正在使用密封类,所以很难想出一个扩展它的好方法。组合它们的另一个挑战是,现在每个Lambda和应用程序都需要在类型级别跟踪其参数的语言。
这个例子并不是很糟糕,因为它非常小,但想象严格的HTML /弱HTML的同样问题。
答案 0 :(得分:3)
这个问题的经典答案是有一个通用AST和一个额外的验证通道。您必须使用语法上格式良好的AST,但不会通过验证(类型检查)。
如果要在类型级别进行区分,则类型检查过程可能会产生新的AST。您可能可以使用与路径相关的类型。
作为旁注,您的示例似乎有一个周期:要创建Lambda
,您需要Variable
,但要创建Variable
,您需要外部{{1} }}
答案 1 :(得分:1)
在决定如何概括时,有时候想到需要对广义结构进行操作的示例函数是有帮助的。因此,请执行一些您希望在绑定树和自由树上执行的操作。采取eta减少:
def tryEtaReduce(x: Expression): Option[Expression] =
x match {
case Lambda(v1, Application(f, v2: Variable)) if v1 == v2 => Some(f)
case _ => None
}
对于上述功能,虽然它有一个明显的丑陋,但如下所述的概括将起作用:
trait AST {
sealed trait Expression
type Scope
case class Variable(scope: Scope, name: String) extends Expression
case class Lambda(v: Variable, inner: Expression) extends Expression
case class Application(function: Expression, input: Expression) extends Expression
}
object BoundAST extends AST {
type Scope = Lambda
}
object FreeAST extends AST {
type Scope = Unit
}
trait ASTOps {
val ast: AST
import ast._
def tryEtaReduce(x: Expression): Option[Expression] =
x match {
case Lambda(v1, Application(f, v2: Variable)) if v1 == v2 =>
Some(f)
case _ =>
None
}
}