输入参数,上限和覆盖

时间:2014-11-14 16:36:22

标签: scala inheritance types pattern-matching

这是一个非常简单的例子:

scala> class A(x: Int) {
     |   def withX(newX: Int): A = new A(newX)
     |   override def toString = x.toString
     | }
defined class A

scala> class B(x: Int) extends A(x) {
     |   override def withX(newX: Int): B = new B(newX)
     | }
defined class B

所以我们有两个类:

  • A,定义了返回withX类型对象的方法A
  • B,扩展A并覆盖其withX方法,以返回B类型的对象。

现在我想创建一个容器,它可以处理AB的实例。它应该有一个方法,可以接受A(或B)的实例,在其上运行withX并返回A的另一个实例(或B })。更具体地说,我想要实现的确切功能(注意返回类型):

scala> val a = new A(0)
a: A = 0

scala> val b = new B(0)
b: B = 0

scala> val testA = new Test[A]
testA: Test[A] = Test@4bf59a3d

scala> val testB = new Test[B]
testB: Test[B] = Test@65e565d1

scala> testA.test(a)
res1: A = 1

scala> testB.test(b)
res2: B = 1

我认为这可以通过具有上限类型参数的类来实现。我试图像这样做,但得到了一个错误:

scala> class Test[T <: A] {
     |   def test(t: T): T = t withX 1
     | }
<console>:9: error: type mismatch;
 found   : A
 required: T
         def test(t: T): T = t withX 1
                               ^

我设法找到的唯一解决方法看起来有些难看。除此之外,如果我们添加一些新类match extends C,则需要重写B子句:

scala> class Test[T <: A] {
     |   def test(t: T): T = (t match {
     |     case b: B => b withX 1
     |     case a: A => a withX 1
     |   }).asInstanceOf[T]
     | }

能否以更优雅的方式实现此功能?我觉得我一般都缺少类型参数,类型边界和所有scala类型系统。

2 个答案:

答案 0 :(得分:3)

虽然可以使用更具体的返回类型覆盖方法,但这不是一个好主意。请考虑以下事项:

class C(x: Int) extends A(x) {
  override def withX(newX: Int): B = new B(x)
}

这是完全合法的,但打破了您的假设,即withX的返回类型与A的子类型与您调用它的实例的(静态已知)类型相同。 / p>

你可以使用F-bounded polymorphism安全地做出这个假设(即在类型系统的支持下):

trait Base[T <: Base[T]] {
  def withX(newX: Int): T
}

class A(x: Int) extends Base[A] {
  def withX(newX: Int) = new A(newX)
  override def toString = x.toString
}

class B(x: Int) extends Base[B] {
  def withX(newX: Int) = new B(newX)
  override def toString = x.toString
}

class Test[T <: Base[T]] {
  def test(t: T): T = t withX 1
}

这需要对您的类型层次结构进行一些更改,但这是一种非常标准的方法来编码方法的返回类型与调用它的实例相同的想法。

答案 1 :(得分:1)

特拉维斯&#39;回答,如果你想编码一个类型支持特定操作(在这种情况下是withX)的事实,你可以考虑使用一个类型类。

class A(x: Int) {
  override def toString = x.toString
}

class B(x: Int) extends A(x)

trait HasWithX[T] {
  def withX(x: Int): T
}

implicit val AHasWithX = new HasWithX[A] {
  def withX(x: Int) = new A(x)
}

implicit val BHasWithX = new HasWithX[B] {
  def withX(x: Int) = new B(x)
}

class Test[T] {
  def test(t: T)(implicit ev: HasWithX[T]): T = ev withX 1
}

示例:

scala> val a = new A(0)
a: A = 0

scala> val b = new B(0)
b: B = 0

scala> val testA = new Test[A]
testA: Test[A] = Test@4bf59a3d

scala> val testB = new Test[B]
testB: Test[B] = Test@65e565d1

scala> testA.test(a)
res1: A = 1

scala> testB.test(b)
res2: B = 1