考虑一个通用函数:
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"
而根本没有发出任何警告或错误,有什么办法吗?
答案 0 :(得分:5)
没有办法在类型系统中强制执行此操作,没有反射。
最好的方法是拥有一个类型类,例如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
是您想要的类型。 (假设你不作弊并手动创建类型类的实例)
答案 1 :(得分:3)
您至少可以在运行时使其失败,如下所示:
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
}