简体   繁体   English

Scala函数方差和覆盖

[英]Scala Function Variance and Overriding

I'm having a little problem understanding variance of methods when overloading. 我在解决重载时方法的方差方面遇到了一些问题。

While this perfectly works due to covariance in the return type 虽然由于返回类型的协方差,这完美地起作用

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                                      
}

this one fails even though functions are contravariant in their parameter types. 即使函数在参数类型中是逆变的,这个也会失败。

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
}

What am I getting wrong here? 我在这里弄错了什么? Any pointers? 有什么指针吗?

Regards, raichoo 问候,raichoo

There are two things going on here: 这里有两件事:

  1. A function and a method are not the same thing 功能和方法不是一回事
  2. Methods are not polymorphic in their parameters' types 方法的参数类型不是多态的

Your tester method is a method, not a Function1 . 您的tester方法是一种方法, 而不是Function1 It can be lifted into a function using the underscore syntax: 可以使用下划线语法将其提升为函数:

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

This function will be contra-variant in its input type. 此功能在其输入类型中将具有矛盾的变体。 (It's worth saying, however, that functions cannot be parameterized and also worth saying that I had to have an instance of Foo or FooTest in order to get a function object for the tester method. This of course follows from the first observation!) (值得一提的是,函数不能参数化 ,并且值得说我必须有一个FooFooTest的实例才能获得tester方法的函数对象。这当然是从第一次观察开始的!)

A function is an object, it cannot be overridden as that makes no sense. 函数是一个对象, 它不能被覆盖 ,因为没有意义。 Methods can be overridden. 方法可以被覆盖。 However, as I say above, the overriding is not polymorphic in the method's parameter types. 但是,正如我上面所说,覆盖在方法的参数类型中不是多态的。 So for example: 例如:

class A {
  def foo(a : Any) = println("A: " + a)
}

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

The two methods in my example above are two separate methods: dynamic dispatch works only on the method target (ie the object on which it is being called). 上面我的例子中的两个方法是两个独立的方法:动态调度仅适用于方法目标(即调用它的对象)。

In the above, example, if you remove the override declaration, the code will compile. 在上面的示例中,如果删除override声明,代码将编译。 If you run the following: 如果您运行以下内容:

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

This is because, although both methods are called foo , they are completely different methods (ie I have overloaded foo , not overridden it). 这是因为,尽管两种方法都被称为foo ,但它们是完全不同的方法(即我重载了 foo ,没有覆盖它)。 It's best understood as being that a method's arguments' (incl their types) form part of that method's unique name . 最好的理解是方法的参数'(包括它们的类型)构成该方法唯一名称的一部分 One method overrides another only if they have exactly the same name . 一种方法只有在具有完全相同的名称时才会覆盖另一种方法。


Essentially you have confused what are two separate and un-related things in your question, which I will put down for clarity: 基本上你已经混淆了你的问题中的两个独立和不相关的东西,为了清楚起见我将放下:

  • The variance annotations on Function1 define what it means for one function to be a subtype of another (and hence assignable to a reference of a given type). Function1上的方差注释定义了一个函数作为另一个函数的子类型的含义(因此赋予给定类型的引用)。
  • Methods can be overridden on subclasses and the language specification outlines rules for when such overriding takes place. 可以在子类上重写方法,并且语言规范概述了何时发生此类重写的规则。

The relevant snippets of the spec: 规范的相关摘要:

Method Types 方法类型

A method type is denoted internally as (Ps)U , where (Ps) is a sequence of parameter names and types (p1 :T1,...,pn :Tn) for some n≥0 and U is a (value or method) type. 方法类型在内部表示为(Ps)U ,其中(Ps)是参数名称和类型的序列(p1 :T1,...,pn :Tn)对于某些n≥0U是a(值或方法) )类型。 This type represents named methods that take arguments named p1, ..., pn of types T1,...,Tn and that return a result of type U . 此类型表示命名方法,该方法接受名为p1, ..., pn的类型为T1,...,Tn并返回类型为U的结果。

Method types do not exist as types of values. 方法类型不作为值的类型存在。 If a method name is used as a value, its type is implicitly converted to a corresponding function type (§6.26). 如果方法名称用作值,则其类型将隐式转换为相应的函数类型(第6.26节)。

Overriding 重写

A member M of class C that matches (§5.1.3) a non-private member M′ of a base class of C is said to override that member. C类的成员M 匹配 (§5.1.3)基类C的非私有成员M′被称为覆盖该成员。 In this case the binding of the overriding member M must subsume (§3.5.2) the binding of the overridden member M′ . 在这种情况下,覆盖成员M的绑定必须包含 (§3.5.2)被覆盖成员M′的绑定。

Conformance 一致性

If Ti ≡ Ti′ for i = 1, ..., n and U conforms to U′ then the method type (p1 : T1,...,pn :Tn)U conforms to (p1′ :T1′,...,pn′ :Tn′)U′ . 如果i = 1, ..., n Ti ≡ Ti′ i = 1, ..., n Ti ≡ Ti′ i = 1, ..., nU符合U′则方法类型(p1 : T1,...,pn :Tn)U符合(p1′ :T1′,...,pn′ :Tn′)U′

Subsumes 涵括

A declaration or definition in some compound type of class type C subsumes another declaration of the same name in some compound type or class type C′ , if one of the following holds. 某些复合类型C类中的声明或定义包含某些复合类型或类类型C′中的同名声明,如果满足下列条件之一。

  • A value declaration or definition that defines a name x with type T subsumes a value or method declaration that defines x with type T′ , provided T <: T′ . 定义具有类型T的名称x的值声明或定义包含定义具有类型T′ x的值或方法声明,前提是T <: T′

You can override and change the return type to a subtype, but while accepting supertype for argument would satisfy the substitution principle, it is not allowed (this is just as in java) The reason is that you can also overload methods (several methods with same name, different arguments count and types) and your method will be considerered an overload. 您可以覆盖并将返回类型更改为子类型,但是虽然接受参数的超类型将满足替换原则,但是不允许(这与java中一样)原因是您也可以重载方法(具有相同的几种方法)名称,不同的参数数量和类型)和你的方法将被认为是一个过载。 I guess this is mainly a question of JVM compatibility and of having a reasonable spec. 我想这主要是JVM兼容性和合理规范的问题。 Overloading already makes the scala spec rather complicated. 重载已经使scala规范变得相当复杂。 Simply routing the overriden method to the overloaded one with the changed signature might be good enough: 简单地将重写方法路由到具有已更改签名的重载方法可能就足够了:

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

What you can do is make a type parameter contravariant, provided in appears only in contravariant position (simplifying, appears as argument type and not as result type) but it is quite different: 你可以做的是使一个类型参数逆变,提供只出现在逆变位置(简化,显示为参数类型而不是结果类型)但它是完全不同的:

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

Well in your second example the signature of tester() in Test declares a Fasel argument but with the overriden signature of FooTest tester() is declared with a Bla as argument. 在你的第二个例子中, Testtester()签名声明了一个Fasel参数,但是使用了FooTest的覆盖签名,者FooTest tester()声明了一个Bla作为参数。 Since Fasel is a subtype of Bla by their extend s hierarchy this is probably wrong. 由于FaselBla的子类型,因此它们的extend层次结构可能是错误的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM