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

Chr*_*ris 5 scala metaprogramming type-erasure

考虑一个通用函数:

def genericFn[T](fn: T => Boolean): Unit = {
  // do something involves T
}
Run Code Online (Sandbox Code Playgroud)

是否可以限制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)
    })
}
Run Code Online (Sandbox Code Playgroud)

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"
Run Code Online (Sandbox Code Playgroud)

上面的代码会打印"Int"而不发出任何警告或错误,有什么办法吗?

Mic*_*jac 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.")
    }

}
Run Code Online (Sandbox Code Playgroud)

使用案例:

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))
                 ^
Run Code Online (Sandbox Code Playgroud)

使用此方法,您可以在编译时强制执行A具有上下文绑定的类型参数NonEraseable是您想要的类型.(假设你没有作弊并手动创建类型类的实例)