Scala 逆变和val

Scala 逆变和val,scala,immutability,contravariance,Scala,Immutability,Contravariance,“val”和“case”如何以及为什么影响类型系统?(特别是差异) 欢迎使用Scala版本2.8.1.final(Java HotSpot(TM)64位服务器虚拟机,Java 1.6.022)。 键入要计算的表达式。 键入:有关详细信息的帮助。 scala>E类[-A] 定义的E类 scala>类别F[-A](值F:E[A]=>单位) :6:错误:逆变型A出现在协变位置,类型=>(E[A])=>值单位f 类别F[-A](值F:E[A]=>单位) ^ scala>案例类别C[-A](f:E[A

“val”和“case”如何以及为什么影响类型系统?(特别是差异)

欢迎使用Scala版本2.8.1.final(Java HotSpot(TM)64位服务器虚拟机,Java 1.6.022)。
键入要计算的表达式。
键入:有关详细信息的帮助。
scala>E类[-A]
定义的E类
scala>类别F[-A](值F:E[A]=>单位)
:6:错误:逆变型A出现在协变位置,类型=>(E[A])=>值单位f
类别F[-A](值F:E[A]=>单位)
^  
scala>案例类别C[-A](f:E[A]=>单位)
:6:错误:逆变型A出现在协变位置,类型=>(E[A])=>值单位f
案例类别C[-A](f:E[A]=>单位)
scala>类别F[-A](F:E[A]=>单位)
定义的F类

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

考虑以下情况:

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
外部不可见,则不会发生此问题。

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或使用case类。

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

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的值,例如我所做的“历史”字段或您所拥有的“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
val-strHashCode:String=>Int={s:Any=>s.hashCode}//用于任何对象的函数
val strComp:Comparator=新的HashCodeComparator()//与任何对象一起工作的Comparator对象

Paul,这是一个蹩脚的回答:如果你不是你,这将是一个否决票!答案有意地指出了这个问题。这意味着在我的示例中,决定因素是
val
case
为构造函数参数生成公共成员?从类型的角度来看,您可以将代码想象为“class E[-a]{def f:a=…},这将a置于协变位置。
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