Типовые безопасные дженерики с использованием TypeTag (с безопасным приведением)

scala

284 просмотра

2 ответа

716 Репутация автора

Я хотел бы реализовать параметрические классы (например List[T]), которые имеют возможность выполнять безопасное приведение типов (например, x.cast[List[U]]).

Под безопасностью типов я подразумеваю, что приведение может вызвать исключение, если тип является неправильным во время выполнения, но гарантируется, что если приведение выполнено успешно, полученное значение будет иметь тип List[U]. (Например, asInstanceOfне делает этого. List(1,2,3).asInstanceOf[List[String]]Будет успешно, но вернет a List, который не содержит Strings.)

Мой подход состоит в том, чтобы пометить все объекты, которые должны поддерживать приведение, с помощью TypeTag. В частности, я бы реализовал черту Typesafeс методом, cast[U]который ожидает неявный TypeTagтип Uи во время выполнения проверяет, являются ли типы подтипами. Вот код, который мне удалось придумать:

import scala.reflect.runtime.universe.TypeTag

trait Typesafe[+T <: Typesafe[T]] {
  val typeTag: TypeTag[_ <: T]

  def cast[U](implicit typeTag: TypeTag[U]) = {
    if (this.typeTag.tpe <:< typeTag.tpe)
      this.asInstanceOf[U]
    else
      throw new ClassCastException(s"Cannot cast ${this.typeTag} to ${typeTag}")
  }
}

Логика такова: класс, Tкоторый наследует, Typesafe[T]должен быть typeTagсоздан с помощью TypeTag[T]. И тогда проверка cast[U]может быть успешной только в том случае, если Tона действительно является подтипом U(в противном случае неявный аргумент castне существует).

Мы можем реализовать эту черту следующим образом (это простой класс-оболочка для Sets):

class TypesafeSet[T](val set : Set[T])
                    (implicit val typeTag:TypeTag[_<:TypesafeSet[T]])
  extends Typesafe[TypesafeSet[T]] {
}

Подтип работает, но, к сожалению, нам нужно extends Typesafe[...]каждый раз указывать пункт.

import scala.collection.immutable.ListSet
class TypesafeListSet[T](set: ListSet[T])
                        (implicit override val typeTag:TypeTag[_<:TypesafeListSet[T]])
  extends TypesafeSet[T](set) with Typesafe[TypesafeListSet[T]] {
}

Вопрос 1: Можем ли мы улучшить этот шаблон, чтобы нам не пришлось повторять extends Typesafe[...]предложение? (В настоящее время, если мы не повторим это, TypesafeListSet[T]не может быть приведен к TypesafeListSet[T].)

Однако в следующем примере у нас есть проблема:

class TypesafeList[T](val list : List[T])
                     (implicit val typeTag:TypeTag[_<:TypesafeList[T]])
  extends Typesafe[TypesafeList[T]] {
  val self = this
  def toSet : TypesafeSet[T] = new TypesafeListSet(ListSet(list : _*))
}

Метод toSetне компилируется, потому что компилятор не может разрешить неявный TypeTag[TypesafeListSet[T]]для new TypesafeListSet. Нужно было бы извлечь TypeTag[T]из typeTag, а затем восстановить TypeTag[TypesafeListSet[T]]из него. Я не знаю, как это возможно.

Вопрос 2: Как получить необходимые TypeTagв toSet? (Один из вариантов можно было бы добавить неявный аргумент типа TypeTag[TypesafeListSet[T]]к toSet, но это толкает проблему наружу, и просачивается детали реализации, а именно , что toSetиспользует ListSet.)

Наконец, можно написать следующий код, нарушающий тип безопасности:

class TypesafeOption[T](val option : Option[T])
                       (implicit val typeTag:TypeTag[_<:TypesafeList[T]])
  extends Typesafe[TypesafeList[T]] {
}

Здесь мы «случайно» использовали TypesafeListв аргументации Typesafeчерту. Это прекрасно компилируется, но это значит, что теперь TypesafeOptionбудет typeTagдля a TypesafeList! (И, следовательно, регистрация castбудет неправильной, и может произойти неправильное приведение.) Я считаю, что такие смешивания могут произойти легко, и было бы хорошо, если бы они могли быть обнаружены компилятором. (В некоторой степени ограничения типа T <: Typesafe[T]уже избегают таких смешиваний (после этого ), но, к сожалению, не того, что в TypesafeOption.)

Вопрос 3 ( ответ ): Можем ли мы уточнить определение черты Typesafeтак, чтобы ее нельзя было Typesafeреализовать таким образом, чтобы она castвелась неправильно?

Наконец, несколько строк кода о том, как использовать эти классы:

import scala.reflect.runtime.universe.typeTag
object Test {
  def main(args:Array[String]) = {
    val list = new TypesafeList(List(1,2,3))
    val set = list.toSet
    val listSet : TypesafeListSet[Int] = set.cast[TypesafeListSet[Int]]
  }
}

К сожалению, этот код не компилируется. Компилятор не находит TypeTagдля вызова new TypesafeList. Нам нужно добавить явно (typeTag[TypesafeList[Int]])в этой строке! (Причина в том, что new TypesafeListожидает a TypeTag[_ <: TypesafeList[Int]], а компилятор недостаточно умен, чтобы увидеть, что он может просто создать a TypeTag[TypesafeList[Int]].)

Вопрос 4: Как мы можем определить, TypesafeListчтобы не нужно явно давать TypeTags?

Наконец, у меня есть несколько вопросов, касающихся общего примера:

Вопрос 5: Есть (по крайней мере) два разных TypeTagкласса во время выполнения, а именно scala.reflect.runtime.universe.TypeTagи scala.reflect.api.TypeTags#TypeTag. Какой из них здесь правильный?

Вопрос 6: Я сравниваю типы, содержащиеся в TypeTags(то есть, typeTag.tpe). Я игнорирую зеркала. Стоит ли сравнивать зеркала? (Другими словами, если два типа тегов имеют совместимые типы, но разные зеркала, будут ли они совместимы по присваиванию?)

Вопрос 7: (возможно, связанный с Вопросом 6.) Что произойдет, если типы одного и того же квалифицированного имени были загружены разными загрузчиками классов? Будет ли приведенный выше код правильным в этом случае? (Т.е., насколько я понимаю , не должно быть возможности привести test.Testзагруженный из загрузчика классов 1 к test.Testзагруженному из загрузчика классов 2).

Вопрос 8 ( ответил ): Правильный ли TypeTagинструмент здесь? Или мне лучше пометить объекты непосредственно с Types? (В конце концов, я сравниваю типы только в cast.) Но, насколько я вижу (из различных обсуждений), TypeTags представлены как решение проблемы маркировки классов для типов безопасности. Почему? Или для чего TypeTag?

Вопрос 9: Есть ли какие-либо комментарии по производительности этого? Сравнение двух типов во время выполнения (с <:<) звучит потенциально дорого ... Есть ли альтернатива? (Я думал о возможном построении карты из TypeTags-pairs to Booleans, которая запоминает, какие типы совместимы с присваиванием. Однако будет ли такой поиск более быстрым? TypeTagНасколько я знаю, у них нет уникальных идентификаторов для быстрого поиска. (GHC использует «отпечатки пальцев» для этого, я думаю.))

Вопрос 10: Есть ли другие наблюдения? Что то я не так делаю? Является ли мой вывод, что castэто безопасно?

Автор: Dominique Unruh Источник Размещён: 17.07.2016 10:21

Ответы (2)


0 плюса

716 Репутация автора

РЕДАКТИРОВАТЬ: Это решение неверно. При использовании этого решения все еще возможно использовать val typeTag = typeTag[Nothing], а затем привести этот класс к любому типу.

Ответ на вопрос 3 ( Можем ли мы уточнить определение черты Typesafeтак, чтобы невозможно было создать экземпляр Typesafeтаким образом, что приведение ведет себя неправильно? )

Это можно сделать, указав «self-type» (см. Спецификацию ). В признаке можно указать тип, Tкоторый должен иметь класс, наследующий класс. Это делается путем написания this:T =>в начале определения черты. В нашем конкретном случае:

trait Typesafe[+T <: Typesafe[T]] {
  this : T =>
  val typeTag: TypeTag[_ <: T]

  def cast[U](implicit typeTag: TypeTag[U]) = {
    if (this.typeTag.tpe <:< typeTag.tpe)
      this.asInstanceOf[U]
    else
      throw new ClassCastException(s"Cannot cast ${this.typeTag} to ${typeTag}")
  }
}

Теперь любой расширяемый класс Typesafe[T]должен иметь тип T. То есть писать можно только в том class X extends Typesafe[T]случае, если Tэто супертип X, и, следовательно, предоставленный тег типа Typesafeбудет гарантированно соответствовать супертипу X.

Автор: Dominique Unruh Размещён: 18.07.2016 01:28

0 плюса

716 Репутация автора

Ответ на вопрос 8 (Является ли TypeTag подходящим инструментом здесь? Или мне лучше пометить объекты непосредственно типами?) :

Одна из причин заключается в том, что a TypeTagимеет параметр типа, который представляет тип с тегом. То есть TypeTag[T]статически обеспечивает, что у нас есть TypeTagдля T. Если бы мы использовали Typeвместо этого, не было бы принудительно, что значение Typesafe.typeTagявляется тегом правильного типа.

Пример:

trait Typesafe[+T <: Typesafe[T]] {
  val typ: Type
  [...]
}

Теперь можно написать:

class TypesafeSet[T](val set : Set[T])
  extends Typesafe[TypesafeSet[T]] {
  val typ = typeOf[String] // !!!
}

Кажется, нет способа избежать этого без TypeTag.

(Это, однако, не объясняет, почему TypeTagнеобходимо иметь зеркало в дополнение к типу.)

Автор: Dominique Unruh Размещён: 20.07.2016 09:05
Вопросы из категории :
32x32