Scala 继承类型不满足父类型要求
Posted
技术标签:
【中文标题】Scala 继承类型不满足父类型要求【英文标题】:Scala inherited type doesn't satisfy parent type requirement 【发布时间】:2021-04-14 00:14:58 【问题描述】:使用 Scala 已经有一段时间了,希望能够使用类型约束进行域建模。在下面的代码中,我正在尝试设计一个域。请原谅神秘的类名,想专注于我手头面临的具体问题。
代码如下:
import scala.collection.mutable
class ClassTypeTest
trait TSM
def fit[T <: TSM](): FR[T]
abstract class FM extends TSM
trait MP[T <: TSM]
def name: String
trait FiM[T <: TSM]
val fittedParams = mutable.HashMap[String, MP[T]]()
class FR[T <: TSM](fm: FiM[T])
// Now define classes related to SMA
//===================================
abstract class SMAP extends MP[SMA]
class SMAO(n: String) extends SMAP
override def name = n
class SMAM(smao: SMAO) extends FiM[SMA]
fittedParams(smao.name) = smao
class SMA extends FM
override def fit[SMA]() =
val fim = new SMAM(new SMAO("x"))
//*******************************
// Following line shows the error:
// Error:(40, 25) type mismatch;
// found : ClassTypeTest.this.SMAM
// required: ClassTypeTest.this.FiM[SMA]
// new FR[SMA](fim)
//*******************************************
new FR[SMA](fim)
我将 SMAM 定义为 extends FiM[SMA]
,那么为什么编译器会抱怨 Type mismatch. Required FiM[SMA], found: SMAM
。我是否定义错误的类型参数或约束之一?
我试图将 fit 方法的类型和 FR 对象限制为 TSM 的子类之一。我如何做到这一点?
【问题讨论】:
【参考方案1】:我将
SMAM
定义为extends FiM[SMA]
,那为什么编译器会抱怨Type mismatch. Required FiM[SMA], found: SMAM.
SMA
in def fit[SMA]() = ...
in
class SMA extends FM
override def fit[SMA]() =
val fim = new SMAM(new SMAO("x"))
new FR[SMA](fim)
是一个类型参数。它是任意类型,而不是SMA
类(类型参数SMA
隐藏类SMA
)。您可以在这里使用任意标识符,例如T
class SMA extends FM
override def fit[T]() =
val fim = new SMAM(new SMAO("x"))
new FR[T](fim)
所以我猜是错误
type mismatch;
found : SMAM
required: FiM[T]
现在很清楚了。
【讨论】:
您认为 OP 是否打算将TSM#fit
中的 T
用作抽象成员类型(或者只是类的 F 有界泛型参数)?
@HTNW 我想为了回答这个问题我们应该了解 OP 的业务逻辑:)
嗨 - 感谢您的快速回复。 @HTNW:我想我不太了解您提到的这两种情况之间的区别,但业务需求是我希望使用 TSM 的子类之一定义 FR 对象。
@DmytroMitin:我想我明白你在说什么:我把 SMA 误认为是类类型,但实际上它是一个任意类型,这就是你用 T 替换 SMA 所显示的。如何然后我应该继续达到我想要达到的目标吗?【参考方案2】:
trait TSM
def fit[T <: TSM](): FR[T]
这定义了一个带有 fit
方法的特征,对于 any 类型 T
返回一个 FR[T]
的实例
class SMA extends TSM
override def fit[SMA]() =
val fim = new SMAM(new SMAO("x"))
new FR[SMA](fim)
这会覆盖 trait 中定义的方法 fit
。方法定义中的SMA
是一个类型参数,而不是对类名的引用。您可以(可能应该)将其替换为 T
,这将是等效的,但不会那么混乱:
override def fit[T <: TSM](): FR[T] = new FR(new SMAM(new SMAO("x")))
这会给你一个更具描述性的错误 - 大意是你试图返回 FR[SMA]
而应该是 FR[T]
。
底线是你不能让子类中的方法比在超类中声明的更“通用”。为了更好地理解为什么会这样,请考虑以下几点:
abstract class Foo extends TSM
val sma: TSM = new SMA()
val foo: FR[Foo] = sma.fit[Foo]()
这个应该工作:sma
是TSM
的一个实例,TSM.fit
应该适用于作为TSM
子类的任何类型参数,Foo
显然是。
但是你写的SMA.fit
只能返回FR[SMA]
,所以如果允许,上面的sn-p将不起作用。
如何解决这个问题? 嗯,这取决于你真正想要。一种可能性是参数化特征本身而不是方法:
trait TSM[T <: TSM[_]]
def fit(): FR[T]
class SMA extends TSM[SMA]
override def fit() = new FR(new SMAM(new SMAO("x")))
【讨论】:
这是 F-bounds 方法,对吧?根据@HTNW 早些时候的评论(tpolecat.github.io/2015/04/29/f-bounds.html)对此进行了一些阅读,看起来在漏洞方面存在一些陷阱,但仍然非常有用的解决方案。 @SamikR 是的,这是一个 F-Bound,请查看 this 了解其他替代方案及其权衡。以上是关于Scala 继承类型不满足父类型要求的主要内容,如果未能解决你的问题,请参考以下文章