【问题标题】:Contravariance and val逆变和val
【发布时间】:2011-02-23 22:24:34
【问题描述】:

“val”和“case”如何以及为什么会影响类型系统? (尤其是方差)

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

【问题讨论】:

    标签: scala immutability contravariance


    【解决方案1】:

    考虑一下:

    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
    

    如果fF 之外不可见,则不会发生此问题。

    【讨论】:

    • 所以这意味着我的示例中的决定因素是valcase 为构造函数参数生成公共成员?
    • 从打字的角度来看,您可以将您的代码视为“class E[-A] { def f : A = ... },这将 A 置于协变位置。
    【解决方案2】:

    您是在问什么是方差?如果您知道方差是什么,这不言自明。没有 "val" 或 "case" 的例子没有涉及 A 的外部可见成员,因此它不会导致方差错误。

    【讨论】:

    • 保罗,这是一个垃圾答案:如果你不是,这将是一个反对票!
    • 答案故意指出问题。
    【解决方案3】:

    “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 或使用案例类。

    【讨论】:

      【解决方案4】:

      这是一个逆变的“输出通道”,它只打印到控制台:

      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) 
      

      由于逆变允许这种类型的“缩小”(即这里从 Any 到 String),类型系统不能公开 T 类型的值,例如我所做的这个“历史”字段,或您拥有的“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
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-07-27
        • 2016-11-03
        • 2015-02-09
        • 2012-04-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多