开发者

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 FooTe开发者_如何学编程st[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


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. 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!)

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 (i.e. the object on which it is being called).

In the above, example, if you remove the override declaration, the code will compile. 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 (i.e. I have overloaded foo, not overridden it). 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).
  • 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. This type represents named methods that take arguments named p1, ..., pn of types T1,...,Tn and that return a result of type 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).

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. In this case the binding of the overriding member M must subsume (§3.5.2) the binding of the overridden member 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′.

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.

  • 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′.


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. I guess this is mainly a question of JVM compatibility and of having a reasonable spec. Overloading already makes the scala spec rather complicated. 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. Since Fasel is a subtype of Bla by their extends hierarchy this is probably wrong.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜