在Scala中封装“递归类型”的麻烦

时间:2014-02-19 15:58:09

标签: scala type-safety parameterized-class

抱歉,我很难找到问题的相关标题。

我想建模以下行为:我设计了一个带有表达式的“语言”,这些表达式封装了标准的Scala类型。表达式可以是变量或表达式序列。在下文中,A可以是标准Scala类型(即Boolean,Int,Double)。我还想实现一种方法来替换表达式中的其他表达式(特别是在序列中)。我尝试了一些我无法编​​译的代码。当我不知道要放什么类型的时候我放了引号,但所有这些都可能是凌乱的。由于它的递归性质,我对序列事物有特殊的麻烦。

sealed trait Expression[A] {
  def replace[B](a: Expression[B], b: Expression[B]): Expression[?]
}

trait Variable[A] extends Expression[A] {
  def replace[B](a: Expression[B], b: Expression[B]) = 
    if (a == this) b else this
}

case class Sequence[A <: Expression[B]](values: Seq[A]) extends Expression[A] {
   def replace[B](a: Expression[B], b: Expression[B]) = 
     if (a == this) b
     else Sequence(values.map(_.replace(a, b)))
}

我当然假设序列是非循环的(序列不能包含自身),因为它会触发无限递归。这些用于实现n-ary矩阵。

感谢您的帮助。

3 个答案:

答案 0 :(得分:3)

看起来你遇到了一个Scala没有优雅解决方案的角落案例。

您需要代替Expression[?]作为返回类型实际上是一种表示“此类型”的类型,它是特征实际混合的类型。 Scala中没有简写方法可以让您以简单的方式表达这一点,并且您也不能使用this.type,因为它太具体,只能在您真正总是返回this时使用。

解决方案通常会快速引入令人费解的样板。例如,Scala集合遇到了同样的问题,像MapLike这样的特征需要编码类似的东西。

我尝试快速修改您的样本以对此类型进行编码。我确定它编译,但我没有运行它:

sealed trait Expression[A, ThisType <: Expression[A, ThisType]] {
  def replace[B,C <: Expression[B,C]](a: Expression[B,C], b: Expression[B,C]): ThisType
}

trait Variable[A] extends Expression[A, Variable[A]] {
  def replace[B,C <: Expression[B,C]](a: Expression[B,C], b: Expression[B,C]) = 
    if (a == this) b.asInstanceOf[Variable[A]] else this
}

case class Sequence[A <: Expression[_,A]](values: Seq[A]) extends Expression[A, Sequence[A]] {
   def replace[B,C <: Expression[B,C]](a: Expression[B,C], b: Expression[B,C]) = 
     if (a == this) b.asInstanceOf[Sequence[A]]
     else Sequence(values.map(_.replace(a, b)))
}

它甚至必须使用一个演员,这通常是一种气味,但我不确定在这种情况下是否有办法避免它。至少它是安全的,我们知道它必须具有正确的类型,只是编译器不知道它。 : - )

答案 1 :(得分:1)

通常在这种情况下,AB之间存在必需的关系。例如,考虑将元素添加到(假设的和高度简化的)序列类型时会发生什么:

class Sequence[+A] {
  def +: [B >: A](b: B): Sequence[B] = {
    /* Build the new sequence with b at the beginning and this sequence's elments after */
  }
}

因此,在您的情况下(如果此模式适用于您),Expression[A]中的签名将为:

def replace[B >: A](a: Expression[B], b: Expression[B]): Expression[B]

答案 2 :(得分:0)

感谢您的回答。我结束了简化问题以获得更简单的解决方案。我简单地“扁平化”了序列的类型参数:

case class Sequence[A] (values: Seq[Expression[A]]) extends Expression[A] {
  def replace[B <: A](a: Expression[A], b: Expression[B]) {
    // (Code in initial question)
  }
}
A中的

Expression现在意味着ExpressionA类型的变量,类型为A的变量序列,序列序列A ...

类型的变量

我在这里失去了表现力,但获得了可用性。 Expression的两个参数变得过于复杂,破坏了Java的互操作性。也许我会稍后尝试改进这个。