【问题标题】:Scala Function Variance and OverridingScala 函数方差和覆盖
【发布时间】:2011-05-16 20:53:25
【问题描述】:

我在理解重载时方法的变化时遇到了一点问题。

虽然由于返回类型的协方差,这完美地工作

class Bla 
class Fasel extends Bla 

trait Test[A] {
 def tester(): Bla = new Bla
}

class FooTest[A](a: A) extends Test[A] {
 override def tester(): Fasel = new Fasel                                      
}

即使函数是逆变的,这个也失败了 参数类型。

class Bla 
class Fasel extends Bla 

trait Test[A] {
 def tester(a: Fasel): Bla = new Bla                                           
}

class FooTest[A](a: A) extends Test[A] {
 override def tester(a: Bla): Fasel = new Fasel
}

我在这里做错了什么?有什么指点吗?

问候, 雷乔

【问题讨论】:

    标签: inheritance scala overriding variance


    【解决方案1】:

    这里发生了两件事:

    1. 一个函数和一个方法are not the same thing
    2. 方法的参数类型不是多态的

    您的tester 方法是一种方法,不是Function1。可以使用下划线语法将其提升为函数:

    val f = (new FooTest[String]).tester _ // Fasel => Bla
    

    这个函数的输入类型是逆变的。 (值得一提的是,函数不能被参数化,还值得一提的是我必须有一个FooFooTest 的实例才能获得tester 的函数对象方法。这当然是从第一次观察得出的!)

    函数是一个对象,它不能被覆盖,因为这是没有意义的。方法可以被覆盖。但是,正如我上面所说,方法的参数类型中的覆盖不是多态的。比如:

    class A {
      def foo(a : Any) = println("A: " + a)
    }
    
    class B extends A {
      override def foo(s : String) = println("B " + s) //will not compile!
    }
    

    上面示例中的两个方法是两个独立的方法:动态分派仅适用于方法目标(即调用它的对象)。

    在上面的示例中,如果您删除 override 声明,代码将编译。如果您运行以下命令:

    (new B).foo(1)   //prints A 1
    (new B).foo("s") //prints B s
    

    这是因为,虽然这两种方法都称为 foo,但它们是完全不同的方法(即我重载了foo,而不是覆盖它)。最好将其理解为方法的参数(包括它们的类型)构成该方法的唯一 name 的一部分。只有当它们具有完全相同的 name 时,一种方法才会覆盖另一种方法。


    基本上你混淆了你的问题中的两个独立和不相关的东西,为了清楚起见,我将把它们记下来:

    • Function1 上的方差注释定义了一个函数成为另一个函数的子类型的含义(因此可分配给定类型的引用)。
    • 方法可以在子类上被覆盖,语言规范概述了何时发生这种覆盖的规则。

    【讨论】:

    • 我在哪里可以在 scala 参考中读到这个?只是为了完整起见:)
    • 知道了:3.3.1 MethodTypes 谢谢 :)
    • 我添加了一个关于方法/功能比较的 SO 问题的链接。此外,关于方法覆盖的部分是字节码定义的一部分(即类文件格式规范)。虽然我希望 Scala 语言规范详细说明如何将 scala 方法“提升”为字节码方法。
    【解决方案2】:

    spec的相关sn-ps:

    方法类型

    方法类型在内部表示为 (Ps)U ,其中 (Ps) 是一系列参数名称和类型 (p1 :T1,...,pn :Tn) 对于某些 n≥0U 是(值或方法)类型。此类型表示命名方法,它们接受名为 T1,...,Tn 类型的参数名为 p1, ..., pn 并返回 U 类型的结果。

    方法类型不作为值类型存在。如果将方法名称用作值,则其类型会隐式转换为相应的函数类型(第 6.26 节)。

    覆盖

    C 类的成员 M 匹配 (§5.1.3) C 基类的非私有成员 M′ 据说会覆盖该成员.在这种情况下,覆盖成员 M 的绑定必须包含(第 3.5.2 节)覆盖成员 M′ 的绑定。

    一致性

    如果Ti ≡ Ti′ for i = 1, ..., nU 符合U′,则方法类型(p1 : T1,...,pn :Tn)U 符合(p1′ :T1′,...,pn′ :Tn′)U′

    归类

    如果满足以下条件之一,则类类型 C 的某些复合类型中的声明或定义包含在某些复合类型或类类型 C′ 中的另一个同名声明。

    • 定义名称 x 类型为 T 的值声明或定义包含定义 x 类型为 T′ 的值或方法声明,前提是 T <: T′

    【讨论】:

      【解决方案3】:

      您可以覆盖并将返回类型更改为子类型,但是虽然接受超类型作为参数会满足替换原则,但这是不允许的(这就像在java中一样)原因是您还可以重载方法(几个具有相同名称、不同参数计数和类型的方法)并且您的方法将被视为重载。我想这主要是 JVM 兼容性和有合理规范的问题。重载已经使 scala 规范相当复杂。简单地将覆盖的方法路由到具有更改签名的重载方法可能就足够了:

      class FooTest[A] extends Test[A] {
         override def test(a: Fasel) : Fasel = test(a.asInstanceOf[Bla])
         def test(a: Bla) : Fasel = new Fasel
      }
      

      您可以做的是使类型参数逆变,提供的 in 仅出现在逆变位置(简化,显示为参数类型而不是结果类型),但它完全不同:

      trait Test[-A] {
        // note the - before A. 
        // You might want to constraint with -A >: Fasel
        def tester(a: A) : Bla = new Bla
      }
      
      class FooTest extends Test[Bla] {
        override def tester(a: Bla): Fasel = new Fasel
      }
      
      val testOfBla: Test[Bla] = new FooTest
      val testOfFasel: Test[Fasel] = testOfBla 
        // you can assign a Test[Bla] to a test[Fasel] because of the -A
      

      【讨论】:

        【解决方案4】:

        在您的第二个示例中,Test 中的 tester() 的签名声明了 Fasel 参数,但 FooTest 的覆盖签名 tester()Bla 作为参数声明。由于FaselBla 的子类型,按其extends 层次结构,这可能是错误的。

        【讨论】:

        • 你没有抓住重点。函数的输入参数是逆变的。因此,OP 对方法覆盖和函数方差之间的区别感到困惑
        猜你喜欢
        • 1970-01-01
        • 2017-04-20
        • 1970-01-01
        • 2018-03-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-06
        相关资源
        最近更新 更多