拆箱标签类型是否安全?
问题描述:
我最近听说过scala中的unboxed tagged类型,当我试图了解它的工作原理时,我发现这个question指出scalaz中实现的问题。其中的一个修复的后果是必须明确的拆礼物标签类型:拆箱标签类型是否安全?
def bmi(mass: Double @@ Kg, height: Double @@ M): Double =
Tag.unwrap(mass)/pow(Tag.unwrap(height), 2)
然后,我认为原来的想法,在那里我可以做这样的事情:
type Tagged[U] = { type Tag = U }
type @@[T, U] = T with Tagged[U]
trait Kilogram
trait Meter
type Kg = Double @@ Kilogram
type M = Double @@ Meter
def bmi(mass: Kg, height: M): Double = mass/pow(height, 2)
所以现在我想知道是否以前在scalaz中发现的问题是特定于它的方法,还是简单实现也可能在擦除,数组或可变参数方面存在问题。事情是我还在学习Scala,所以我对它的类型系统的理解是非常有限的,我自己也弄不明白。
答
从类型安全角度来看这是不安全的。 T @@ U
是T
的子类型,并且可以使用T @@ U
的实例,即使它是偶然的,也需要使用T
的实例。考虑以下内容
type Tagged[U] = { type Tag = U }
type @@[T, U] = T with Tagged[U]
object Tag {
def apply[@specialized A, T](a: A): A @@ T = a.asInstanceOf[A @@ T]
}
trait Compare[A] { def compare(a1: A, a2: A): Int }
def useCompare[A: Compare](l: List[A]): Option[A] =
l.foldLeft(Option.empty[A])((xs, x) =>
xs.fold(Some(x))(xxs =>
if (implicitly[Compare[A]].compare(xxs, x) <= 0) Some(xxs)
else Some(x)))
implicit def intCompare: Compare[Int] = new Compare[Int] {
def compare(a1: Int, a2: Int): Int =
a1.compareTo(a2)
}
trait Max
implicit def intCompareMax: Compare[Int @@ Max] = new Compare[Int @@ Max] {
def compare(a1: Int @@ Max, a2: Int @@ Max): Int =
a1.compareTo(a2) * -1
}
scala> val listInts: List[Int] = List(1, 2, 3, 4)
listInts: List[Int] = List(1, 2, 3, 4)
scala> val min = useCompare(listInts)
min: Option[Int] = Some(1)
scala> val listIntMaxs: List[Int @@ Max] = listInts.map(Tag[Int, Max])
listIntMaxs: List[@@[Int,Max]] = List(1, 2, 3, 4)
scala> val max = useCompare(listIntMaxs)
max: Option[@@[Int,Max]] = Some(4)
好吧,一切都很酷吧?这就是为什么T @@ U
存在。我们希望能够为它创建一个新类型并为其定义新类型。不幸的是,当你的同事出现并执行一些有效的重构并意外地破坏你的业务逻辑时,一切都不好。
scala> val max = useCompare(listIntMaxs ::: List.empty[Int])
max: Option[Int] = Some(1)
糟糕
在这种情况下,使用子类型的,具有对List[+A]
类型参数的协方差组合引起的错误。当需要List[Int]
时,可以使用List[Int @@ Max]
替代。
这是真的,即使同事应该使用::: List.empty [Int @@ Max]',它也可能发生。但这不是一个bug,因为unboxed标签类型,简单的子类型会导致它,所以我会寻找一些其他的东西。 – andrepnh
添加':::List.empty [Int @@ Max]'而不是'::: List.empty [Int]'不会导致错误。我想你可能不了解这里发生的事情。出现这个问题是因为我们使用子类型来生成一个新类型,以便为它重新定义类型类。你是正确的,简单的子类型会导致它,这正是问题所在。想象一下,如果可以定义'class MaxInt extends Int',然后在上面的示例中用'MaxInt'替换Int @@ Max'。这本质上是一回事。 – drstevens
你是bmi的例子并不是你为什么会使用这种模式的一个好例子。查看源代码中包含的示例。我从'Max'模拟了我的示例https://github.com/scalaz/scalaz/blob/series/7.3.x/example/src/main/scala/scalaz/example/TagUsage.scala#L76-L79 – drstevens