Безопасны ли распакованные маркированные типы?


Недавно я услышал о распакованных тегированных типах в 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 3

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] вызвало ошибку. A List[Int @@ Max] может быть заменен везде, где требуется a List[Int].