Использование TypeTag в Scala 2.10

Я копаю новый API отражения scala и не могу понять, почему следующий фрагмент не работает должным образом. Учитывая иерархию (постарался максимально упростить):

import scala.reflect.runtime.universe._

trait TF[A] {
  implicit def t: TypeTag[A]

  def f[T <: A: TypeTag]: PartialFunction[Any, A] = {
    case msg: T if typeOf[T] =:= typeOf[A] => msg
  }
}

class TFilter[T: TypeTag] extends TF[T] {
  def t = typeTag[T]
}

case class Foo(x: Int)

Я ожидаю, что метод f будет фильтровать объекты данного типа. Таким образом, следующий фрагмент должен вернуть Seq[Foo]

val messages = Seq(1, "hello", Foo(1))

val tFilter = new TFilter[Foo]
messages collect tFilter.f[Foo]

И на самом деле он возвращает Seq[Foo], но с другими неотфильтрованными сообщениями, что звучит как ошибка.

res1: Seq[Foo] = List(1, hello, Foo(1))

Вопрос. Я неправильно использую TypeTag или это дефект нового API отражения?

PS0. Пробовал с Scala 2.10.0-RC1 и 2.10.0-RC2

PS1. Обходной путь состоит в том, чтобы заменить TypeTag на Manifest, поэтому следующий код collect on sequence вернет List(Foo(1)), как и ожидалось.

trait MF[A] {
  implicit def m: Manifest[A]

  def f[T <: A: Manifest]: PartialFunction[Any, A] = {
    case msg: T if typeOf[T] =:= typeOf[A] => msg
  }
}

class MFilter[T: Manifest] extends MF[T] {
  def m = manifest[T]
}

Обновление: то же самое с новой версией Scala 2.10.0-RC2.


person 4e6    schedule 05.11.2012    source источник
comment
Может быть полезно добавить в код предупреждение, которое вы получаете для case msg: T: warning: abstract type T in type pattern T is unchecked since it is eliminated by erasure. Я не вижу этого предупреждения со старым подходом Manifest.   -  person Steve    schedule 05.11.2012
comment
@som-snytt, думаю, это копается. Отсутствие практики английского. В следующий раз постараюсь быть внимательнее :)   -  person 4e6    schedule 06.11.2012


Ответы (4)


Поэтому я думаю, что ключевая проблема здесь заключается в том, что вам нужно сопоставить тип msg, но его тип времени компиляции — Any (из объявления PartialFunction). По сути, вам нужны разные TypeTag для каждого элемента в вашем List[Any]. Но поскольку все они имеют тип времени компиляции Any в силу того, что все они помещены в один и тот же список, вы не получите более конкретного TypeTag.

Я думаю, что вы, вероятно, захотите использовать ClassTag вместо TypeTag:

trait TF[A] {
  implicit def t: ClassTag[A]

  def f: PartialFunction[Any, A] = {
    case msg: A => msg
  }
}

class TFilter[T: ClassTag] extends TF[T] {
  def t = classTag[T]
}

case class Foo(x: Int)

val messages = Seq(1, "hello", Foo(1), List(1), List("a"))
messages collect new TFilter[Foo].f // produces List(Foo(1))

Как указывает Аджран, как и в версии Manifest, вам нужно знать обо всех ограничениях типов времени выполнения, включая вопросы стирания и упаковки:

messages collect new TFilter[List[Int]].f // produces List(List(1), List("a"))
messages collect new TFilter[Int].f // produces List()
messages collect new TFilter[java.lang.Integer].f // produces List(1)

Есть несколько предложений о том, как сделать TypeTag более полезным для сопоставления с образцом (например, SI-6517), но я думаю, что это поможет только тогда, когда вы сопоставляете объект с полезным TypeTag, а не объект с типом времени компиляции Any.

person Steve    schedule 05.11.2012
comment
Но из-за scaladoc, ClassTag является более слабым частным случаем TypeTag, поэтому я предполагаю, что последний тоже должен работать. - person 4e6; 05.11.2012
comment
если вы посмотрите на ссылку в моем ответе, вы увидите, что это не так: мы определенно обсуждали использование тегов типов для облегчения сопоставления с образцом, но, я думаю, у Адриана сейчас есть более важные дела. - person Arjan; 05.11.2012
comment
Верно, в документации написано ClassTags являются более слабым частным случаем scala.reflect.api.TypeTags#TypeTags, поскольку они обертывают только класс среды выполнения данного типа, тогда как TypeTag содержит всю информацию о статическом типе. Но если ваш статический тип Any, TypeTag вам все равно не поможет, и лучшее, что вы можете сделать, это класс среды выполнения из класса ClassTag. - person Steve; 06.11.2012
comment
@Arjan, Стив, som-snytt, спасибо за помощь. Подумайте, теперь я понимаю, как это работает. Все ответы содержат полезную информацию, но я должен выбрать один. - person 4e6; 06.11.2012

на самом деле вы не проверяете тип msg здесь, компилятор предупредит вас, что msg: T стерто, поэтому все, что вам остается проверить, это то, что тип, определенный в TFilter, совпадает с типом, определенным в функции f.

Похоже, сопоставление с образцом «помогает» Manifest и ClassTag, поэтому msg: T действительно правильный тип. Если вы попробуете это с примитивами или List[T], это не будет работать правильно.

val mFilter = new MFilter[Int]
messages collect mFilter.f[Int] // res31: Seq[Int] = List()

val messages = List(List(1), List("a"))
val mFilter = new MFilter[List[Int]]
messages collect mFilter.f[List[Int]] // res32: List[List[Int]] = List(List(1), List(a))

Посмотрите на это обсуждение: http://grokbase.com/t/gg/scala-user/126p8eh1w0/how-to-make-typetag-work-in-a-pattern

ошибка «Использовать TypeTags при сопоставлении с шаблоном, в противном случае стертые типы»: здесь

person Arjan    schedule 05.11.2012

Просто для удовольствия:

import scala.reflect._
import scala.reflect.runtime.{currentMirror=>cm,universe=>ru}
import ru._

object Test extends App {
  type MyTag[A] = TypeTag[A]
  //type MyTag[A] = ClassTag[A]

  trait TF[A] {
    implicit def t: MyTag[A]

    def f[T <: A: MyTag]: PartialFunction[Any, A] = {
      //case msg: T => msg            // ok for ClassTag
      case msg: T @unchecked if matching[T](msg) => msg
      //case msg: T if typeOf[T] =:= typeOf[A] => msg
    }
    def matching[T](a: Any)(implicit tt: TypeTag[T]) =
      (cm reflect a).symbol.toType weak_<:< tt.tpe
  }

  case class TFilter[A: MyTag]() extends TF[A] {
    def t = implicitly[MyTag[A]]
  }

  trait Foo { def x: Int }
  case class Bar(x: Int) extends Foo
  case class Baz(x: Int) extends Foo

  val messages = Seq(1, Bar(0), "hello", Baz(1))
  println(messages collect TFilter[Foo].f[Foo])
  println(messages collect TFilter[Foo].f[Bar])
}
person som-snytt    schedule 06.11.2012

Спасибо всем за отзывы. Думаю, я нашел причину, по которой ClassTag следует использовать при сопоставлении с образцом.

Мне удалось найти [SI-5143] Сопоставление шаблонов для абстрактных типов не работает, и это связанный коммит объясняет, что должен быть экземпляр ClassTag, чтобы сделать проверка шаблона.

Итак, да, я использовал TypeTag неправильно; в случае сопоставления с образцом я должен использовать ClassTag.

person 4e6    schedule 05.11.2012
comment
Смотрите мой обновленный ответ. Я думаю, что настоящая проблема в том, что msg имеет тип времени компиляции Any. Это не абстрактный тип, это конкретный тип. Это просто бесполезный бетонный тип. ;-) - person Steve; 06.11.2012