如何在编译时强制执行非泛型类型
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 类型的实例)
考虑一个通用函数:
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 类型的实例)