consider a generic function:
def genericFn[T](fn: T => Boolean): Unit = {
// do something involves T
}
is it possibile to restrict T
(at compile time) to be a simple type, not a type like List[Int]
?
the underling problem I want to solve is something like this:
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)
})
}
the addCase
function would result in type erasure warning, which could be solved by requiring ClassTag
like: def addCase[T: ClassTag](...
, but ClassTag
still can't guard against calls like:
addCase[List[Int]](_ => {println("Int"); true})
addCase[List[String]](_ => {println("String"); false})
actorReceive(List("str")) // will print "Int"
the above code will print "Int"
while not issuing any warning or error at all, is there any way out?
There is no way to enforce this in the type system as-is, without reflection.
The nicest way to do this would be to have a type-class such as NonEraseable[A]
, that provides evidence that a type has no type parameters that would be erased at runtime. An implicit NonEraseable[A]
in scope should mean that A
has no type parameters. Seeing as these would be tedious to manually create, an implicit macro can do the job:
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.")
}
}
Use case:
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))
^
Using this, you can enforce at compile time that a type parameter A
with a context bound NonEraseable
is the kind of type you want. (Assuming you don't cheat and manually create instance of the type class)
You can at least get it to fail at run-time as follows:
def addCase[T: ClassTag](handler: T => Boolean): Unit =
if (classTag[T].runtimeClass.getTypeParameters.nonEmpty) {
// throw an exception
} else {
// the main code
}
Compile-time failure can be achieved using a macro instead of a function (approximate, untested):
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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With