Scala函数方差和覆盖

时间:2010-11-28 13:42:30

标签: inheritance scala override variance

我在解决重载时方法的方差方面遇到了一些问题。

虽然由于返回类型中的协方差而完美地起作用

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
}

我在这里弄错了什么?有什么指针吗?

此致 raichoo

4 个答案:

答案 0 :(得分:11)

这里有两件事:

  1. 功能和方法 are not the same thing
  2. 方法的参数类型
  3. 不具有多态性

    您的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,而不是重写它)。最好的理解是方法的参数'(包括它们的类型)构成该方法的唯一名称的一部分。一种方法只有在具有完全相同的名称时才会覆盖另一种方法。


    基本上你已经混淆了你的问题中两个单独和不相关的东西,为了清楚起见,我将放下这些东西:

    • Function1上的方差注释定义了一个函数作为另一个函数的子类型意味着什么(因此可赋值到给定类型的引用)。
    • 可以在子类上覆盖方法,语言规范概述了何时发生此类重写的规则。

答案 1 :(得分:4)

规范的相关摘要:

方法类型

  

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

     

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

<强>重写

  

M的成员C 匹配(§5.1.3)基类{{1}的非私有成员M′据说要覆盖那个成员。在这种情况下,覆盖成员C的绑定必须包含(§3.5.2)被覆盖成员M的绑定。

<强>一致性

  

如果M′Ti ≡ Ti′的{​​{1}}符合i = 1, ..., n,则方法类型U符合U′

<强>涵括

  

类型(p1 : T1,...,pn :Tn)U的某种复合类型中的声明或定义包含某些复合类型或类类型(p1′ :T1′,...,pn′ :Tn′)U′中的同名声明,如果满足下列条件之一。

     
      
  • 定义名称x且类型为T的值声明或定义包含一个值或方法声明,用于定义C C′,提供x
  •   

答案 2 :(得分:0)

你可以覆盖并将返回类型更改为子类型,但是虽然接受参数的超类型将满足替换原则,但是不允许(这与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
}

你可以做的是使一个类型参数逆变,只提供在逆变位置(简化,显示为参数类型而不是结果类型),但它是完全不同的:

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

答案 3 :(得分:-1)

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