Contravariance and val
How and why does 'val' and 'case' affect the type system? (Especially the variance)
Welcome to Scala version 2.8.1.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_22).
Type in expressions to have them evaluated.
Type :help for more information.
scala> class E[-A]
defined class E
scala> class F[-A](val f: E[A] => Unit)
<console>:6: error: contravariant type A occurs in covariant position in type => (E[A]) => Unit of value f
class F[-A](val f: E[A] => Unit)
^
scala> case class C[-A](f: E[A] => Unit)
<console>:6: error: contravariant type A occurs in covariant position in type => (E[A]) => Unit of value f
case class C[-A](f: E[A] => Unit)
scala> class F[-A](f: E[A] =&g开发者_高级运维t; Unit)
defined class F
Consider this:
trait Equal[-A] { def eq(a1: A, a2: A): Boolean }
val e = new Equal[Option[Int]] {
def eq(a1: Option[Int], a2: Option[Int]) = a1 forall (x => a2 forall (x ==))
}
// Because Equal is contra-variant, Equal[AnyRef] is a subtype of Equal[String]
// Because T => R is contra-variant in T, Equal[AnyRef] => Unit is a supertype
// of Equal[String] => Unit
// So the follow assignment is valid
val f: Equal[AnyRef] => Unit = (e1: Equal[String]) => println(e1.eq("abc", "def"))
// f(e) doesn't compile because of contra-variance
// as Equal[Option[Int]] is not a subtype of Equal[AnyRef]
// Now let's tell Scala we know what we are doing
class F[-A](val f: Equal[A @uncheckedVariance] => Unit)
// And then let's prove we are not:
// Because F is contra-variant, F[Option[Int]] is a subtype of F[AnyRef]
val g: F[Option[Int]] = new F(f)
// And since g.f is Equal[Option[Int]] => Unit, we can pass e to it.
g.f(e) // compiles, throws exception
If f
is not visible outside F
, this problem can't happen.
Are you asking what variance is? If you know what variance is, this is self-explanatory. The example without "val" or "case" has no externally visible members involving A, so it can't induce a variance error.
The 'val' means that the field is externally visible. Consider:
val f: E[Any] => Unit = { ... }
val broken: F[Int] = new F[Any](f) // allowed by -A annotation
val f2: E[Int] => Unit = broken.f // must work (types match)
val f3: E[Int] => Unit = f // type error
Basically, we managed to unsafely cast f without acting for it explicitly. This only works is f is visible, i.e. if you define it as a val or use a case class.
Here's a contravariant "output channel" that just prints to the console:
class OutputChannel[-T] {
def write(t:T) = println(t);
}
Here it is in action:
val out:OutputChannel[Any] = new OutputChannel[Any]
out.write(5)
Nothing interesting yet. The cool thing about contravariance is you can now safely assign this output channel to one that accepts any subclass of T:
val out2:OutputChannel[String] = out
out2.write("five")
out2.write(55) //wont compile
Now, imagine if we added a history tracking to the output channel - to give back a List of things that have been sent out thus far.
//!!! as you've seen code like this won't compile w/ contravariant types!!!!
class OutputChannel[-T] {
var history:List[T] = Nil
def write(t:T) = {
history = history :+ t;
println(t);
}
}
If the above did compile, the user of the String-based output channel would have a problem:
//history(0) is an Int - runtime exception (if scala allowed it to compile)
val firstStringOutputted:String = out2.history(0)
Since contravariance allows this "narrowing" of types (ie from Any to String here), the type system cannot expose values of type T, such as this "history" field I did, or the "f" field you had.
Other famous "contrarians" are Function and Comparators:
val strHashCode:String => Int = { s:Any => s.hashCode } //function which works with any object
val strComp:Comparator<String> = new HashCodeComparator() //comparator object which works with any object
精彩评论