Безопасны ли неупакованные теговые типы?

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


person andrepnh    schedule 08.04.2016    source источник


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

person drstevens    schedule 08.04.2016
comment
Это правда, и это определенно может произойти, даже если коллега должен ::: List.empty[Int @@ Max]. Но это не ошибка из-за неупакованных помеченных типов, простое подтипирование тоже может вызвать ее, поэтому я какое-то время поищу что-нибудь еще. - person andrepnh; 11.04.2016
comment
Добавление ::: List.empty[Int @@ Max] вместо ::: List.empty[Int] не приведет к ошибке. Я думаю, вы, возможно, не понимаете, что здесь происходит. Проблема возникает из-за того, что мы используем подтипирование для создания нового типа, чтобы переопределить для него классы типов. Вы правы, что простое подтипирование тоже может вызвать это, что и является проблемой. Представьте, если бы можно было определить class MaxInt extends Int, а затем заменить Int @@ Max на MaxInt в моем примере выше. Это по сути одно и то же. - person drstevens; 11.04.2016
comment
Ваш пример ИМТ не является хорошим примером того, почему вы должны использовать этот шаблон. См. примеры, включенные в исходный код. Я смоделировал свой пример из Max github.com/scalaz/scalaz/blob/series/7.3.x/example/src/main/ - person drstevens; 11.04.2016
comment
Я поиграл со scalaz, и теперь я думаю, что полностью понимаю вашу точку зрения. Проблема в том, что мы говорили о двух разных способах использования теговых типов. Пока я просто искал удобный способ не смешивать аргументы одного типа, а разные понятия, ваш пример ближе к моноидам. Я заглянул в библиотеку только для того, чтобы не изобретать велосипед, и эта ошибка заставила меня задуматься, следует ли мне использовать неупакованные теговые типы для чего-то несущественного. Спасибо. - person andrepnh; 12.04.2016