Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why method defined like "cons[B >: A](v: B)" accepts argument of type which is not supertype of A?

I am studying variance in scala right now, and I think I have a good understanding of contravariance. For example given trait List[-A], I know that List[Int] is a supertype of List[AnyVal].

But say that I have the following trait:

trait List[+A] {
  def cons(hd: A): List[A]
}

Why is cons parameter type wrong?

Why it is necessary to have def cons[B >: A](v: B): List[B] ?

For example:

val animal_list: List[Animal] = List(tiger, dog)

if we call:

animal_list.cons(tiger)

since Tiger <: Animal, doesn't cons ran into problem? Since B is Tiger and A is Animal and B >: A is not true.

like image 821
testing Avatar asked May 19 '16 21:05

testing


1 Answers

Why is cons's parameter type wrong?

trait List[+A] {
  def cons(hd: A): List[A]
}

Compiler give you error:
covariant type A occurs in contravariant position in type A of value hd
because method parameters count as contravariant positions, but A is covariant.

Let's imagine that this method declaration would compile. Then we could do:

class ListImpl[A] extends List[A] {
  override def cons(hd: A): List[A] = ???
}

val strings: List[String] = new ListImpl[String]
val values: List[Any] = strings // OK, since List[String] <: List[Any] (in List[A], A is covariant)
values.cons(13) // OK(??), since values's static type is List[Any], so argument of cons should be Any, and 13 conforms to type Any

Is the last line above really OK? We are calling cons on values. values is the same as strings, and strings is object of type ListImpl[String]. So cons invocation in the last line is expecting String argument, but we are passing Int, because values's static type is List[Any] and Int conforms to Any. Something is definitely wrong here - which line is to blame? The answer is: cons method declaration. To fix this issue, we have to remove covariant type parameter A from contravariant position (in cons declaration). Alternatively we can make A non-covariant.

See also these questions: #1, #2.

... doesn't cons ran into problem?

trait List[+A] {
  def cons[B >: A](v: B): List[B]
}

val animal_list: List[Animal] = List(tiger, dog)  // We are assuming that List.apply and concrete implementation of List is somewhere defined.

No, animal_list.cons(tiger) invocation is type-correct.

I assume that Animal is common supertype of Dog and Tiger, and that dog and tiger are instances of Dog and Tiger respectively.

In animal_list.cons(tiger) invocation, both A and B type parameters are instantiated to Animal, so cons method takes form of:

def cons[Animal >: Animal](v: Animal): List[Animal]

Animal >: Animal constraint is satisfied because:

Supertype and subtype relationships are reflexive, which means a type is both a supertype and a subtype of itself. [source]

The argument to cons is Tiger which conforms to type Animal, so the method invocation is type-correct.

Notice that if you force B to be instantiated to Tiger, like animal_list.cons[Tiger](tiger), then this invocation won't be type-correct, and you'll get compiler error.

See similar example here.

like image 50
TeWu Avatar answered Sep 24 '22 19:09

TeWu