2011-02-23 91 views
2

如何和爲什麼「VAL」和「案例」影響類型系統? (尤其是方差)逆變和Val

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] => Unit)  
defined class F 

回答

4

考慮一下:

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 

如果f是不可見的外F,也不會發生這個問題。

+0

所以這意味着我的例子中的決定因素是'val'和'case'爲構造函數參數生成公共成員? – ladrl 2011-02-24 07:46:14

+0

從打字的角度來看,你可以認爲你的代碼是「E類[-A] {def f:A = ...},這使得A處於協變位置。」 – 2011-02-24 17:25:56

+0

@ladrl這是正確的。 – 2011-02-24 17:58:55

3

你問的是什麼差異?如果你知道什麼是差異,這是不言自明的。沒有「val」或「c​​ase」的示例沒有涉及A的外部可見成員,因此它不會引起方差錯誤。

+0

保羅,這是一個扯淡的答案:如果你不是*你*,這將是一個downvote! – 2011-02-23 23:51:39

+0

答案故意指出這個問題。 – extempore 2011-02-24 00:45:34

1

在「VAL」表示該字段是外部可見的。考慮:

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 

基本上,我們設法不安全地施放了f而沒有明確地採取行動。這僅適用於f是可見的,即,如果將其定義爲val或使用案例類。

0

這裏有一個逆變 「輸出通道」,它只是打印到控制檯:

class OutputChannel[-T] { 
    def write(t:T) = println(t); 
} 

這是在行動:

val out:OutputChannel[Any] = new OutputChannel[Any] 
out.write(5) 

沒有什麼有趣呢。關於逆變很酷的事情是,你可以現在這個輸出通道安全地分配到一個接受的T任何子類:

val out2:OutputChannel[String] = out 
out2.write("five") 
out2.write(55) //wont compile 

現在,想象一下,如果我們增加了一個歷史跟蹤到輸出通道 - 回饋的列表迄今爲止發出的東西。

//!!! 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); 
    } 
} 

如果上述沒有進行編譯時,基於字符串的輸出信道的用戶將有一個問題:由於逆變允許此類型的「變窄」(即,從任何給字符串這裏

//history(0) is an Int - runtime exception (if scala allowed it to compile) 
val firstStringOutputted:String = out2.history(0) 

),類型系統不能暴露類型T的值,例如我所做的這個「history」字段,或者你所擁有的「f」字段。

其他著名的「逆向」的功能和比較器:

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