在Scala中使用密封的case类创建两个相关的AST

时间:2014-05-14 12:01:06

标签: scala abstract-syntax-tree case-class lambda-calculus sealed

每当我必须在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的同样问题。

2 个答案:

答案 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
    }
}