Covariance contravariance error in Scala - scala

I tried to compile the following piece of code:
class A[-T]
class B[-T] extends A[A[T]]
I obtain the following error:
error: contravariant type T occurs in covariant position in type [-T]A[A[T]]{def <init>(): B[T]} of class B
Why is this an error?

This is an error because A[A[T]] is covariant in type T.
Definition of covariant type from Wikipedia:
Within the type system of a programming language, a typing rule or a
type constructor is:
covariant if it preserves the ordering of types (≤), which orders types from more specific to more generic;
contravariant if it reverses this ordering;
Consider the following:
class Fruit
class Banana extends Fruit
and
Banana <: Fruit
A[Banana] :> A[Fruit] // Because A is contravariant in type T
A[A[Banana]] <: A[A[Fruit]] // Again because A is contravariant in type T
The last statement means that A[A[T]] is covariant since it preserves the ordering of types from more specific to more generic.
So it possible to do:
scala> type AA[+T] = A[A[T]]
defined type alias AA
scala> type AAA[-T] = A[A[A[T]]]
defined type alias AAA
But the following will lead to error:
scala> type AA[-T] = A[A[T]]
<console>:15: error: contravariant type T occurs in covariant position in type [-T]A[A[T]] of type AA
type AA[-T] = A[A[T]]
^
scala> type AAA[+T] = A[A[A[T]]]
<console>:15: error: covariant type T occurs in contravariant position in type [+T]A[A[A[T]]] of type AAA
type AAA[+T] = A[A[A[T]]]
^
And finally returning to the original question there is the same violation of the variance rule in class B definition since the base class A[A[T]] is covariant in type T by construction.
To understand why it is prohibited let's assume it is possible:
class B[-T] extends A[A[T]]
In this case we get:
B[Fruit] <: B[Banana] <: A[A[Banana]]
so
val fruits: B[Fruit] = new B[Fruit]
val bananas1: B[Banana] = fruits
val bananas2: A[A[Banana]] = bananas1
Now we have a value bananas2 of type A[A[Banana]] which points to an instance of A[A[Fruit]] which violates type-safety since A[A[Banana]] <: A[A[Fruit]].

<init> is the constructor of A (or B), defined by the compiler.
The problem with the code is that B[-T] extends A[A[T]] leads to a contradiction.
Take this example:
class A[-T]
class B[-T] extends A[A[T]]
class Animal
class Cat extends Animal
For short-hand, let's use A <: B to signify that A is a sub-type of B.
Since both A and B are contravariant over their type parameters T, and Cat <: Animal, then
A[Animal] <: A[Cat]
B[Animal] <: B[Cat]
Since A[Animal] <: A[Cat], then (again because of the contravariance of A):
A[A[Cat]] <: A[A[Animal]]
Also, by the definition of B:
B[Cat] = A[A[Cat]]
B[Animal] = A[A[Animal]]
We already know that B[Animal] <: B[Cat], but if we translate these types to their above equivalances, we get:
B[Animal] <: B[Cat] => A[A[Animal]] <: A[A[Cat]]
But we already showed that A[A[Cat]] <: A[A[Animal]]. We end up with an impossible situation.

Related

Scala Existential type weird behavior

I have the following in my REPL:
scala> trait T[A]
defined trait T
scala> :kind T[A] forSome {type A}
T[_]'s kind is A
scala> :kind T[_]
T[_]'s kind is A
Now when i do:
trait e[_] extends T[_]
I get
error: class type required but T[_] found
However the following works:
scala> trait e[_] extends T[Int]
defined trait e
With
scala> :kind T[Int]
T[Int]'s kind is A
Why is T[Int] treated differently from T[_], while they are of the same Kind ?
It's not about kind, it's about whether a type is a class type (including traits) or not.
You can write
type T <: U
for every type U but
trait T extends U
only for a class type U.
Subtyping and inheritance/subclassing are different.
The type corresponding to a trait T[A] is a class type for every type A. So T[Int] is a class type. (When you write extends A[B] it's important that A is a class type, B can be arbitrary type.)
Existential type (like T[_] aka T[A] forSome {type A}) is not a class type. (You can also think of T[_] as a supertype of all T[A]. Actually, T[_] is the least upper bound of all T[A]).

Why are all invariant generic class positions invariant in type parameter lists in Scala?

I'm a bit perplexed by the strictness of the typechecker below — it seems that the invariant T position of Inv[T] is also invariant within Variantish's parameter list:
scala> class Inv[T]
defined class Inv
scala> class Variantish[+T, +TVar <: Inv[T]]
<console>:12: error: covariant type T occurs in invariant position in type <: Inv[T] of type TVar
class Variantish[+T, +TVar <: Inv[T]]
^
Variant types can normally occur in what look like invariant argument-list positions legally, e.g. with object-protected visibility:
class Variantish[+T](protected[this] var v: Inv[T])
and it seems that the following would be just as typesafe:
class Variantish[+T, +TVar <: Inv[T]](protected[this] var v: TVar)
Need that check mentioned above be so strict?
From the language specification (emphasis mine), about conformance (ie T' is a super-type of T):
Type constructors T and T′ follow a similar discipline. We characterize T and T′ by their type parameter clauses [a1,…,an] and [a′1,…,a′n], where an ai or a′i may include a variance annotation, a higher-order type parameter clause, and bounds. Then, T conforms to T′ if any list [t1,…,tn] -- with declared variances, bounds and higher-order type parameter clauses -- of valid type arguments for T′ is also a valid list of type arguments for T and T[t1,…,tn]<:T′[t1,…,tn].
This is really difficult to understand (IMHO), but I believe it means that for Variantish to be covariant in T, you would have to be able to write
Variantish[Dog, TVar] <: Variantish[Animal, TVar]
for any TVar for which Variantish[Animal, TVar] makes sense. But this doesn't even make sense (let alone have any truth value) for some of those TVar, such as Inv[Animal]. That's why it is forbidden in that place.
I didn't quite understand #cyrille-corpet's answer so I expanded it with some examples.
class Inv[T]
class Variantish[+T, +TVar <: Inv[T]]
trait Animal
class Dog extends Animal
class AnimalInv extends Inv[Animal]
class DogInv extends Inv[Dog]
val a: Variantish[Animal, Inv[Animal]] = new Variantish[Animal, AnimalInv]
val b: Variantish[Animal, Inv[Animal]] = new Variantish[Animal, DogInv]
val c: Variantish[Animal, Inv[Animal]] = new Variantish[Dog, AnimalInv]
val d: Variantish[Animal, Inv[Animal]] = new Variantish[Dog, DogInv]
a is valid since Animal <: Animal and AnimalInv <: AnimalInv are both true.
b is invalid since DogInv <: AnimalInv is false.
c is valid since Dog <: Animal and AnimalInv <: AnimalInv are both true.
d is invalid since DogInv <: AnimalInv is false.
So as these show TVar cannot be covariant.
Even in the case of d where the dynamic type is valid, it is not a subtype of the static type.
I suspect if we looked at all the places where we may be using TVar in Variantish then it doesn't need to be a type parameter. As #concat pointed out any variance errors you may encounted can be solved by using the object-protected access modifier.

Understanding List[+A] for Covariance

Looking at the source for List.scala:
sealed abstract class List[+A] extends ...
...
def isEmpty: Boolean
def head: A
def tail: List[A]
List[+A] is covariant based on the +A. Does this mean that, it's possible to create a List[T] where T can be the type itself, or any of its sub-classes?
example:
scala> trait Kid
defined trait Kid
scala> case class Boy(name: String) extends Kid
defined class Boy
scala> case class Girl(name: String) extends Kid
defined class Girl
scala> val list: List[Kid] = List(Boy("kevin"), Girl("sally"))
list: List[Kid] = List(Boy(kevin), Girl(sally))
Observe that head and tail's types are A and List[A], respectively. Once we've defined List[+A], then head and tail's A is also covariant?
I've read this StackOverflow answer 3 or 4 times, but I don't understand yet.
Your example does not relate to variance. Moreover, head and tail have nothing to do with variance too.
scala> val list: List[Kid] = List(Boy("kevin"), Girl("sally"))
list: List[Kid] = List(Boy(kevin), Girl(sally))
This would work even if List weren't covariant, because Scala will automatically deduce common supertype of Boy and Girl, that is, Kid, and type of the expression on the right side will be List[Kid], exactly what you require on the left side.
The following, however, doesn't work because java.util.List is not covariant (it is invariant since it is Java type):
scala> import java.util.{List => JList, Arrays}
import java.util.{List=>JList, Arrays}
scala> trait Kid
defined trait Kid
scala> case class Boy(name: String) extends Kid
defined class Boy
scala> val list1 = Arrays.asList(Boy("kevin"), Boy("bob"))
list1: java.util.List[Boy] = [Boy(kevin), Boy(bob)]
scala> val list2: JList[Kid] = list1
<console>:12: error: type mismatch;
found : java.util.List[Boy]
required: java.util.List[Kid]
Note: Boy <: Kid, but Java-defined trait List is invariant in type E.
You may wish to investigate a wildcard type such as `_ <: Kid`. (SLS 3.2.10)
val list2: JList[Kid] = list1
^
Arrays.asList method has signature like this:
def asList[T](args: T*): java.util.List[T]
As java.util.List[T] is invariant, it is impossible to assign JList[Boy] (list1) to JList[Kid] (list2). And there is a reason: if you could, then because JList is mutable, you could also add anything extending Kid (not only Boy) into the same list, breaking type safety.
On the other hand, scala.List will work in exactly the same situation:
scala> val list1 = List(Boy("kevin"), Boy("bob"))
list1: List[Boy] = List(Boy(kevin), Boy(bob))
scala> val list2: List[Kid] = list1
list2: List[Kid] = List(Boy(kevin), Boy(bob))
That is because scala.List is covariant in its type parameter. Note that covariant List type works as if List[Boy] were subtype of List[Kid], very similar to the case when you can assign everything to a variable of type Any because every other type is a subtype of Any. This is very helpful analogy.
Contravariance works in a very similar way, but in other direction. Consider this trait:
trait Predicate[-T] {
def apply(obj: T): Boolean
}
object Predicate {
// convenience method to convert functions to predicates
def apply[T](f: (T) => Boolean) = new Predicate[T] {
def apply(obj: T) = f(obj)
}
}
Note the - before T parameter: it is a contravariance annotation, that is, Predicate[T] is defined to be contravariant in its only type parameter.
Recall that for covariant list List[Boy] was a subtype of List[Kid]. Well, for contravariant predicate it works in the opposite way: Predicate[Kid] is a subtype of Predicate[Boy], so you can assign a value of type Predicate[Kid] to a variable of type Predicate[Boy]:
scala> val pred1: Predicate[Kid] = Predicate { kid => kid.hashCode % 2 == 0 }
pred1: Predicate[Kid] = Predicate$$anon$1#3bccdcdd
scala> val pred2: Predicate[Boy] = pred1
pred2: Predicate[Boy] = Predicate$$anon$1#3bccdcdd
If Predicate[T] weren't contravariant, we wouldn't be able to assign pred1 to pred2, though it is completely legitimate and safe: obviously, predicates defined on supertypes can easily work on subtypes.
In short, variance affects type compatibility between parameterized types. List is covariant, so you can assign a value of type List[Boy] to a variable of type List[Kid] (in fact, for any T extending S, you can assign a value of type List[T] to a variable of type List[S]).
On the other hand, because, Predicate is contravariant, you can assign Predicate[Kid] to Predicate[Boy] (that is, for any T extending S, you can assign a value of type Predicate[S] to a variable of type Predicate[T]).
If a type is invariant in its type parameter, neither of the above can be done (as is demonstrated by JList).
Note the correspondence between parameterized types and their parameters:
T <: S ===> List [T] <: List [S] (covariance)
T <: S ===> Predicate[S] <: Predicate[T] (contravariance)
This is the reason why the first effect is called *co*variance (T <: S on the left, and
..T.. <: ..S.. on the right), and the second is *contra*variance (T <: S on the left, but ..S.. <: ..T.. on the right).
Whether to make your own parameterized types covariant or contravariant or invariant depends on your class responsibilities. If it may only return values of generic type, then it makes sense to use covariance. List[T], for example, only contains methods which return T, never accept T as a parameter, so it is safe to make it covariant in order to increase expressiveness. Such parameterized types can be called producers.
If your class only accepts values of the generic type as a parameter, not returns them (exactly like Predicate above which has single method def apply(obj: T): Boolean), then you can safely make it contravariant. Such parameterized types can be called consumers
If your class both accepts and returns values of the generic type, i.e. it is both a producer and a consumer, then you have no choice but to leave the class invariant in this generic type parameter.
This idiom is usually called "PECS" ("Producer extends, Consumer super") because variance annotations are written extends and super in Java.

What is the reason this type parameter syntax doesn't compile?

Say I have:
class Class[CC[A, B]]
class Thing[A, B <: Int]
class Test extends Class[Thing] // compile error here
I get the compiler error:
kinds of the type arguments (cspsolver.Thing) do not conform to the expected kinds of the type parameters (type CC) in class Class. cspsolver.
Thing's type parameters do not match type CC's expected parameters: type C's bounds <: Int are stricter than type B's declared bounds >: Nothing <: Any
However when I modify the code such that it looks like this:
class Class[CC[A, B]]
class Thing[A, B] {
type B <: Int
}
class Test extends Class[Thing]
it compiles fine. Aren't they both functionally equivalent?
The reason is given in the compiler message. In Class you expect an unrestricted CC, while Thing has the restriction that the second type argument must be <: Int. One possibility is to add the same constraint to Class as in
class Class[CC[A,B <: Int]]
class Thing[A, B <: Int]
class Test extends Class[Thing]
Elaborating on Petr Pudlák's explanation, here is what I assume happens: The compiler tries to unify CC[A, B] with Thing[A, B <: Int]. According to the declaration of B in CC, B's upper type-bound is Any, which is picked to instantiate B. The B in Thing, however, is expected to have Int a its upper type-bound, and the compiler thus fails with the error message you got.
This is necessary in order to preserve soundness of the type system, as illustrated in the following sketch. Assume that Thing defines an operation that relies on the fact that its B <: Int, e.g.,
class Thing[A, B <: Int] {
def f(b: B) = 2 * b
}
If you declared Class as
class Class[CC[A,B]] {
val c: CC
}
and Test as
class Test extends Class[Thing] {
val t: Thing
}
without the compiler complaining, then you could make the following call
new Test().t.f(true)
which is obviously not safe.

Scala: Why lower bounds in existential type declaration not enforced?

Assume the following declarations made into the Scala repl:
class Animal
class Bird extends Animal
class Chicken extends Bird
type SubType = t forSome { type t <: Bird }
type SuperType = t forSome { type t >: Bird }
As I expect, SubType is of a type that conforms to Bird. Since Animal is a superclass of Bird, a variable of type SubType cannot hold value of type Animal:
scala> val foo: SubType = new Animal
<console>:10: error: type mismatch;
found : Animal
required: SubType
val foo: SubType = new Animal
and yet this corollary is not as I expect:
scala> val foo: SuperType = new Chicken
foo: SuperType = Chicken#1fea8dbd
The assignment succeeds, and so do these:
scala> val foo: SuperType = 2
foo: SuperType = 2
scala> val foo: SuperType = "wtf?"
foo: SuperType = wtf?
scala>
Again, here's SuperType:
type SuperType = t forSome { type t >: Bird }
According to SLS 4.3,
A type declaration type t [tps ] >: L <: U declares t to be an abstract type with lower bound type L and upper bound type U.
So I'm declaring t to be an abstract type with lower bound Bird. Chicken is not a superclass of Bird, neither are String nor Int.
I thought that maybe it's because a Chicken is an Any and a SuperType can store an Any. But if I change the declaration of SuperType to this:
type SuperType = t forSome { type t >: Bird <: Animal}
to set an upper bound of Animal nothing seems to change.
Questions
First
How are my assignments of values of types Chicken Int and String to a SuperType variable allowed by existential clauses { type t >: Bird } and { type t >: Bird <: Animal}?
Second
What is the significance of the word "abstract" in the quoted specification that "A type declaration type t [tps ] >: L <: U declares t to be an abstract type..." What would the difference in meaning be if the word "abstract' were not there?
I think the confusion is between the use of the term "bound" and "constraint". The "<:" and ">:" syntax constructs are for specifying type constraints, not bounds. When you say
{ type t >: Bird }
you are merely constraining the type "t" such that inhabitants of that type must share a superclass with Bird. Since Chicken and Bird both extend Animal, the constraint is met, and Scala allows the assignment. This is also why your "bogus" assignments work (String and Int both share the superclass Any with Bird. The later constraint you proposed
{ type t >: Bird <: Animal }
properly restricts your examples of String and Int, as there exists no type in the type hierarchy of String or Int that meets the constraint of being a supertype of Bird while also being a subtype of Animal. However, that will still allow your initial assignment.
Edit: Other answers on SO say that both are called bounds. Shows how much I know. :)