逆变和值

发布于 2024-10-19 02:16:11 字数 772 浏览 2 评论 0原文

'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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

离线来电— 2024-10-26 02:16:11

考虑一下:

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

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.

昔梦 2024-10-26 02:16:11

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

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.

无边思念无边月 2024-10-26 02:16:11

“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 类。

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.

尐籹人 2024-10-26 02:16:11

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

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

如果上面的代码确实编译通过,基于 String 的输出通道的用户将会遇到一个问题:

//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

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文