How to enforce non-generic type at compile time

consider the general function:

def genericFn[T](fn: T => Boolean): Unit = { // do something involves T } 

can you restrict T (at compile time) to a simple type, rather than a type of type List[Int] ?


The undermining 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 will prevent type erasure, which could be solved by requiring ClassTag as: def addCase[T: ClassTag](... , but ClassTag still cannot protect 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" without giving any warnings or errors at all, is there any way out?

+5
source share
2 answers

In the type system, it is not possible to force use of as-is, without reflection.

The best way to do this is to have a type-type, such as NonEraseable[A] , which indicates that the type does not have type parameters that will be deleted at run time. Implicit NonEraseable[A] in scope should mean that A has no type parameters. Seeing that it will be tiring to create manually, an implicit macro can complete the task:

 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 provide, at compile time, a type A parameter with a context NonEraseable β€” the type of type you want. (Assuming you are not tricking or manually creating an instance of a type class)

+5
source

You can at least get it crashing at runtime as follows:

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

Compilation failure can be achieved using macro instead of function (approximate, unchecked):

 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 } 
+3
source

Source: https://habr.com/ru/post/1235221/


All Articles