5

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?

Chris
  • 1,094
  • 1
  • 11
  • 26
  • 3
    Since you're dealing with akka here, I have found that trying to do 'generic' messages is going to be a major pain, especially if any kind of serialisation is going on. On a more general level, the kind of Int and List[Int] are both *, so a single type parameter with no other restrictions will not differentiate them. A standard typeclass solution would probably suffice, or to you could use something more advanced such as from the link dk14 provided. – melps Nov 04 '15 at 17:44
  • 1
    You can add protection from `List`: http://stackoverflow.com/questions/15962743/using-context-bounds-negatively-to-ensure-type-class-instance-is-absent-from-s – dk14 Nov 04 '15 at 17:44
  • but I'm not sure how to generelize it to any polymorphic type – dk14 Nov 04 '15 at 17:45
  • 1
    For what is worth, a [TypeTag](http://docs.scala-lang.org/overviews/reflection/typetags-manifests.html) can maybe help you. It contains the type information a `ClassTag` does not. – Daniel Langdon Nov 05 '15 at 03:45

2 Answers2

5

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)

Michael Zajac
  • 55,144
  • 7
  • 113
  • 138
3

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
  }
Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
  • "+1", but isn't macro too hardcore for that, you can't even test it in same module. Maybe there is some shapeless-based solution. P.S. I've tried several evidence-based, but it didn't work because scala's support for generics and high-order is quite strange. Things like that are allowed for some reason: `implicit def notSimple[T, M[_]](t: M[T]) = ""; notSimple(9)` Changing return type to `M[T]` killed the compiler. How the ***** 9 is M[T]? :) – dk14 Nov 04 '15 at 19:46