A canonical example of patching up an otherwise covariant class is as follows:
abstract class Stack[+A] { def push[B >: A]( x: B ) : Stack[B] def top: A def pop: Stack[A]
Now, if I remove the implicit covariance and manually annotate the class, I get this:
abstract class Stack[A] { def push[B >: A]( x: B ) : Stack[B] def top [B >: A]: B def pop [B >: A]: Stack[B] def cast[B >: A]: Stack[B] }
(Quick correctness proof: a Stack[A]
has elements of type A
, so if B
is more permissive we can always return an A
in place of B
. Similarly, given any stack of A
, we can use it in place of a stack of B
if B can accept A.)
But now I m a little confused: there should be contravariance somewhere here, but all of the subtype relations here seem to be the same. What happened?
To elaborate anymore, we define a contravariant functor F
such that (a -> b) -> (F b -> F a)
. In particular, the functor F a
on a -> r
is contravariant, as (a -> b) -> ((b -> r) -> (a -> r))
simply by composing the functions. From a formalism perspective, I expect arrows to be flipping. So from a purely syntactic perspective, I get confused when no arrows are flipping (but there should be!) Is my annotated way of writing the Scala simply a "natural" representation of the contravariance of functions, such that you don’t even notice it? Is my abstract class wrong? Is there something misleading about the second presentation?