如何在编译时强制执行非泛型类型

How to enforce non-generic type at compile time

考虑一个通用函数:

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" 而根本不会发出任何警告或错误,有什么办法吗?

你至少可以让它在 运行 时失败,如下所示:

def addCase[T: ClassTag](handler: T => Boolean): Unit =
  if (classTag[T].runtimeClass.getTypeParameters.nonEmpty) {
    // throw an exception
  } else {
    // the main code
  }

可以使用 macro 而不是函数(近似值,未经测试)来实现编译时失败:

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
  }

没有反射,没有办法在类型系统中强制执行此操作。

最好的方法是使用一个类型 class,例如 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))
                 ^

使用它,您可以在编译时强制 具有上下文绑定NonEraseable 的类型参数A 是您想要的类型。 (假设您不作弊并手动创建 class 类型的实例)