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]()

这个应该工作:smaTSM 的一个实例,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 继承类型不满足父类型要求的主要内容,如果未能解决你的问题,请参考以下文章

Scala 学习笔记之隐式参数和隐式转换并用

scala中类的继承关系

java中有哪些类型的方法,如何调用它们?

未满足 boost::asio 读取处理程序类型要求

java 既然子类能继承父类的所有属性与方法,那子类不能不定义成员变量?

类继承