当Scala类型参数用作基类的参数时,它是否可以引用自身?

时间:2011-08-27 08:41:47

标签: generics scala

如果我有以下Scala类型层次结构:

// Base traits
trait TA[X <: TA[X,Y], Y <: TB[X,Y]]
trait TB[X <: TA[X,Y], Y <: TB[X,Y]]
trait TC[X <: TA[X,_]]

// More specific traits
trait TI[X <: TI[X,Y], Y <: TJ[X,Y]] extends TA[X,Y]
trait TJ[X <: TI[X,Y], Y <: TJ[X,Y]] extends TB[X,Y]
trait TK[X <: TI[X,_]] extends TC[X]

// Concrete class that should implement TK, but does not compile
class Z extends TK[TI[_,_]]

// What is needed to be able to get class Z to compile
// The reference of X to itself has been removed.
trait TC2[X <: TA[_,_]]
trait TK2[X <: TI[_,_]] extends TC2[X]
class Z2 extends TK2[TI[_,_]]
TC将成为某种TA的通用经理。

TK将是更具体的TA(TI)的更具体的管理者。

Z将是管理实现TI的任何对象的具体实现。

Z不合法,但Z2是。不幸的是,TC和TK比TC2和TK2更具特异性。那么有没有办法用TC和TK来代替TC,而不是TC2和TK2?

[编辑]我在原来的问题中没有说,我有点理解为什么Z不健全。我真正想知道的是,如果有办法说出这样的话:

class Z3 extends TK[TI[TI,_]]

2 个答案:

答案 0 :(得分:7)

当您具有复杂的相互递归类型边界时,通常可以帮助您查看是否可以使用抽象类型成员将问题转换为等效的问题。如果我们机械地这样做,你的基础和更具体的特征最终看起来像,

// Base traits
trait TA {
  type X <: TA
  type Y <: TB
} 
trait TB {
  type X <: TA
  type Y <: TB
} 

trait TC {
  self =>
  type X <: TA { type X <: self.X }
} 

// More specific traits
trait TI extends TA {
  type X <: TI
  type Y <: TJ
} 

trait TJ extends TB { 
  type X <: TI
  type Y <: TJ
}

trait TK {
  self =>
  type X <: TI { type X <: self.X }
} 

现在我们可以直接定义Z as,

class Z extends TK {
  type X = TI
}

请注意,TA,TB和TI,TJ成员的定义基本相同。因为这些类型现在是类型成员,所以我们可以将它们分解为常见的基类型,如此,

// Base traits
trait T0 {
  type X <: TA
  type Y <: TB
} 
trait TA extends T0
trait TB extends T0

trait TC {
  self =>
  type X <: TA { type X <: self.X }
}

// More specific traits
trait T1 extends T0 {
  type X <: TI
  type Y <: TJ
}

trait TI extends TA with T1
trait TJ extends TB with T1

trait TK extends TC {
  self =>
  type X <: TI { type X <: self.X }
}

class Z extends TK {
  type X = TI
}

答案 1 :(得分:3)

这将是不健全的。这就是原因,简化的例子。我们不需要两个通用参数,我们也不需要子类型TI,TJ和TK

trait TA[X <: TA[X]]
trait TC[X <: TA[X]]
class Z extends TC[TA[_]]
  

类型参数[TA [_]]不符合特征TC的类型参数   边界[X&lt;:TA [X]]

让我们看看为什么这个声明不合理,它失败是正确的。 我们添加一些代码。我将TC更改为class,以便将代码转换为java。一个trait在scala中也可以。

trait TA[X <: TA[X]] {def f(x: X) }
class TC[X <: TA[X]] {def g(x: X) = x.f(x)}

它工作正常,x: X也是TA[X],所以它有一个例程f,它将接受X

如果我们尝试改为

class TC2[X <: TA[_]] {def g(x: X) = x.f(x)}

然后失败了。我们知道f上有一个x方法,但我们不知道需要什么类型作为参数,我们无法知道x会没问题。实际上,假设我们定义了

class T1 extends TA[T1]] {def f(t1: T1) = {}; def t1Only = println("only in T1")}
class T2 extends TA[T1]] {def f(t1: T1) = t1.t1Only }

现在,如果允许TC2,我可以创建一个TC2[T2],使用g致电T2,这会在{{1}中调用f {}为T2。这是不允许的,因为T2没有方法T2

这说明为什么t1Only无法接受TC作为参数,因为TA[_]]允许T2与方法g不兼容。那么为什么我们无法使用参数Z定义TA[_]。它在java中完全相同,并且与原始代码一样。


编辑:我对自己的回答感到有些内疚。由于我给出了为什么不应该允许的原因,我认为会有一个简单的解决方法。它失败了,我没有时间进一步调查,并在没有提及的情况下发布。解决方法是自我类型。如果我们这样做

trait TA[X <: TA[X]] {self: X => }

然后我们无法定义T2 extends TA[T1]。因此原始代码更受限制。但是我们必须接受原始代码的限制,因为它不健全。所以它不仅仅是一种语法技巧,它必须使事情变得不可能。我认为T2 extends TA[T1]可能不是预期的东西,而且这是防止的一件事。

显然,不是,同样的错误。现在我没有一个例子,为什么它不应该工作。这当然不意味着没有。

然后我看了一下Miles的解决方案,想知道为什么T2 extends TA[T1]的可能性不会伤害它。同样,放弃TBYTITJTK

trait TA{type X}
trait TC{self => type X <: TA{type X <: self.X}
class Z extends TC{type X = TA}

这个编译。我们可以做到

trait T1 extends TA{type X = T1}
trait T2 extends TA{type X = T1}

但有一件事我们不能做:

trait TA {type X <: TA; def f(x: X)}
trait TC {self => 
  type X <: TA{type X <: self.X} 
  def g(x: X) = x.f(x)
}

对于g的{​​{1}}参数,我们在x上收到以下错误

  

类型不匹配;发现:x.type(底层类型为TC.this.X)   必需:x.X

f并不完全是原始版本(原来,TC是允许的)。之所以如此,是因为g type X <: self.XTA更强TC[X <: TA[X]]。如果我们写这个,我们回到原来的错误,type X = self.X不编译。因此,此Z介于原始TCTC)和type X = self.X之间(不知道TA的X)。同样,对原始代码的限制,我们无法定义TC2

如果限制是可以接受的,那你没问题。我不知道如何将它写成泛型(也不知道如何用抽象类型成员编写自我类型g)。迈尔斯绝对是专家,我相信他能够分辨出它是如何完成的,或者说它是不可能的。