Безопасны ли распакованные маркированные типы?
Недавно я услышал о распакованных тегированных типах в 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, специфичными для его подхода, или если простая реализация также может иметь проблемы со стиранием, массивами или varargs. Дело в том, что я все еще изучаю scala, поэтому мое понимание этой системы типов довольно ограничено, и я не мог понять ее самостоятельно.
1 ответ:
Это небезопасно с точки зрения безопасности типов.
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]
вызвало ошибку. AList[Int @@ Max]
может быть заменен везде, где требуется aList[Int]
.