Как принудительно применять неродовой тип во время компиляции
Рассмотрим обобщенную функцию:
def genericFn[T](fn: T => Boolean): Unit = {
// do something involves T
}
Можно ли ограничить T
(во время компиляции) простым типом, а не таким, как List[Int]
?
Проблема подчиненных, которую я хочу решить, выглядит примерно так:
var actorReceive: Receive = PartialFunction.empty
def addCase[T](handler: T => Boolean): Unit = {
actorReceive = actorReceive orElse ({
case msg: T => // call handle at some point, plus some other logic
handler(msg)
})
}
Функция addCase
приведет к предупреждению стирания типа, которое можно решить, потребовав ClassTag
Как: def addCase[T: ClassTag](...
, но ClassTag
все еще не может защитить от вызовов типа:
addCase[List[Int]](_ => {println("Int"); true})
addCase[List[String]](_ => {println("String"); false})
actorReceive(List("str")) // will print "Int"
Приведенный выше код напечатает "Int"
, не выдавая никакого предупреждения или ошибка вообще, есть ли выход?
2 ответа:
Нет никакого способа обеспечить это в системе типов как есть, без рефлексии.
Самый лучший способ сделать это-иметь класс типа, такой как
NonEraseable[A]
, который обеспечивает доказательство того, что тип не имеет параметров типа, которые были бы удалены во время выполнения. НеявноеNonEraseable[A]
в области видимости должно означать, чтоA
не имеет параметров типа. Поскольку их было бы утомительно создавать вручную, неявный макрос может выполнить эту работу:import scala.language.experimental.macros import scala.reflect.macros.blackbox.Context trait NonEraseable[A] object NonEraseable { implicit def ev[A]: NonEraseable[A] = macro evImpl[A] def evImpl[A](c: Context)(implicit tt: c.WeakTypeTag[A]): c.Expr[NonEraseable[A]] = { import c.universe._ val tpe = weakTypeOf[A] if(tpe.dealias.typeArgs.isEmpty) c.Expr[NonEraseable[A]](q"new NonEraseable[$tpe] {}") else c.abort(c.enclosingPosition, s"$tpe contains parameters that will be erased at runtime.") } }
Пример использования:
def onlySimple[A : NonEraseable](value: A): Unit = println(value) scala> onlySimple(1) 1 scala> onlySimple(List(1, 2, 3)) <console>:13: error: List[Int] contains parameters that will be erased at runtime. onlySimple(List(1, 2, 3)) ^
Используя это, вы можете принудить во время компиляции что параметр типа
A
с контекстной привязкойNonEraseable
является типом, который вы хотите. (При условии, что вы не обманываете и вручную создаете экземпляр класса type)
Вы можете, по крайней мере, заставить его отказать во время выполнения следующим образом:
def addCase[T: ClassTag](handler: T => Boolean): Unit = if (classTag[T].runtimeClass.getTypeParameters.nonEmpty) { // throw an exception } else { // the main code }
Сбой компиляции может быть достигнут с помощью макроса вместо функции (приблизительной, непроверенной):
def addCase[T](handler: T => Boolean): Unit = macro addCaseImpl def addCaseImpl[T: c.WeakTypeTag](c: Context)(handler: c.Expr[T => Boolean]): c.Expr[Unit] = if (c.weakTypeOf[T].typeParams.nonEmpty) { c.abort(c.enclosingPosition, "Generic types not allowed in addCase") } else { // generate code for main line }