我正在实施数据结构。虽然它没有直接混合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
...
这看起来像一个肮脏的把戏。如果typer正常拒绝这个,那么如何才能安全并使用uncheckedVariance
关闭?
答案 0 :(得分:2)
这可能是因为它使用@uncheckedVariance
注释来环绕类型系统并忽略方差检查。
只需import scala.annotation.unchecked.uncheckedVariance
并注释要禁用方差检查的类型:
def to[Col[_]](implicit cbf: CanBuildFrom[Nothing, A, Col[A @uncheckedVariance]]): Col[A @uncheckedVariance]
查看更完整的解释in the related answer。
答案 1 :(得分: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
签名中重复元素类型(作为上限)。
但是,如果我们能够想象一个因忽略方差而导致运行时错误的案例,那仍然无法回答?
答案 2 :(得分: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])