Как принудительно применять неродовой тип во время компиляции


Рассмотрим обобщенную функцию:

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 5

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
  }