6

我正在实现一个数据结构。虽然它没有直接混入 Scala 的任何标准集合特征,但我想包含一个to[Col[_]]方法,给定一个构建器工厂,它可以生成标准 Scala 集合。

现在假设这个,复制自GenTraversableOnce

trait Foo[+A] {
  def to[Col[_]](implicit cbf: CanBuildFrom[Nothing, A, Col[A]]): Col[A]
}

这失败了error: covariant type A occurs in invariant position

那么如何才能GenTraversableOnce做到这一点呢?我可以在源代码中看到,他们添加了一个annotation.unchecked.uncheckedVariance...

这看起来像一个肮脏的把戏。如果打字机正常拒绝这个,这怎么能安全并用 关闭uncheckedVariance

4

3 回答 3

2

方差检查是类型检查中非常重要的一部分,跳过它很容易导致运行时类型错误。在这里,我可以通过打印来演示填充了无效运行时值的类型。不过,我还不能让它因类型转换异常而崩溃。

import collection.generic.CanBuildFrom
import collection.mutable.Builder
import scala.annotation.unchecked.uncheckedVariance

trait Foo[+A] {
  def toCol[Col[_]](implicit cbf: CanBuildFrom[Nothing, A, Col[A @uncheckedVariance]]): Col[A @uncheckedVariance]
}

object NoStrings extends Foo[String] {
  override def toCol[Col[_]](implicit cbf: CanBuildFrom[Nothing, String, Col[String]]): Col[String] = {
    val res : Col[String] = cbf().result
    println("Printing a Col[String]: ")
    println(res)
    res
  }
}

case class ExactlyOne[T](t : T)

implicit def buildExactlyOne = new CanBuildFrom[Nothing, Any, ExactlyOne[Any]] {
  def apply() = new Builder[Any, ExactlyOne[Any]]  { 
    def result = ExactlyOne({}) 
    def clear = {}
    def +=(x : Any) = this
  }
  def apply(n : Nothing) = n
}

val noStrings : Foo[Any] = NoStrings
noStrings.toCol[ExactlyOne]

这里println(res)res : Col[String]印刷品ExactlyOne(())。但是,ExactlyOne(())没有类型Col[String],表明类型错误。

为了在尊重方差规则的同时解决问题,我们可以将不变代码移出特征并仅保留协变部分,同时使用隐式转换将协变特征转换为不变辅助类:

import collection.generic.CanBuildFrom

trait Foo[+A] {
  def to[R](implicit cbf: CanBuildFrom[Nothing, A, R]): R
}

class EnrichedFoo[A](foo : Foo[A]) {
  def toCol[Col[_]](implicit cbf: CanBuildFrom[Nothing, A, Col[A]]): Col[A] = 
    foo.to[Col[A]]
}

implicit def enrich[A](foo : Foo[A]) = new EnrichedFoo(foo)

case class Bar[A](elem: A) extends Foo[A] {
  def to[R](implicit cbf: CanBuildFrom[Nothing, A, R]): R = {
    val b = cbf()
    b += elem
    b.result()
  }
}

val bar1 = Bar(3)
println(bar1.toCol[Vector])
于 2013-03-08T20:29:05.887 回答
2

我阅读了@axel22 提到的另一个问题的链接。不过,它似乎仍然不是真正的原因(允许GenTraversableOnce对变体和不变集合都起作用——它在 中协变的A)。

例如,以下内容在不强制键入器的情况下正常工作:

import collection.generic.CanBuildFrom

trait Foo[+A] {
  def to[A1 >: A, Col[_]](implicit cbf: CanBuildFrom[Nothing, A1, Col[A1]]): Col[A1]
}

case class Bar[A](elem: A) extends Foo[A] {
  def to[A1 >: A, Col[_]](implicit cbf: CanBuildFrom[Nothing, A1, Col[A1]]): Col[A1]= {
    val b = cbf()
    b += elem
    b.result()
  }
}

在我看来,这将是正确的签名。但是当然,它变得丑陋:

val b = Bar(33)
b.to[Int, Vector]

因此,我对使用的解释仅仅是为了避免在签名@uncheckedVariance中重复元素类型(作为上限) 。to

但是,如果我们可以想象一个由于忽略方差而导致运行时错误的情况,那仍然没有答案?

于 2013-03-08T18:56:51.743 回答
2

可以,因为它使用@uncheckedVariance注解绕过类型系统并忽略方差检查。

简单地import scala.annotation.unchecked.uncheckedVariance注释您希望禁用方差检查的类型:

def to[Col[_]](implicit cbf: CanBuildFrom[Nothing, A, Col[A @uncheckedVariance]]): Col[A @uncheckedVariance]

在相关答案中查看更完整的解释。

于 2013-03-08T18:45:35.953 回答