3

我最近听说过 scala 中未装箱的标记类型,当我试图了解它的工作原理时,我发现这个问题指出了 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,所以我对它的类型系统的理解非常有限,我自己无法弄清楚。

4

1 回答 1

0

从类型安全的角度来看,它是不安全的。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 @@ Max]可以在List[Int]需要 a 的地方替换A。

于 2016-04-08T22:42:36.213 回答