scalatypeclassimplicitsingleton-typescala-2.13

Is there anyway, in Scala, to get the Singleton type of something from the more general type?


I have a situation where I'm trying to use implicit resolution on a singleton type. This works perfectly fine if I know that singleton type at compile time:

object Main {

    type SS = String with Singleton

    trait Entry[S <: SS] {
        type out
        val value: out
    }

    implicit val e1 = new Entry["S"] {
        type out = Int
        val value = 3
    }
    implicit val e2 = new Entry["T"] {
        type out = String
        val value = "ABC"
    }

    def resolve[X <: SS](s: X)(implicit x: Entry[X]): x.value.type = {
        x.value
    }

    def main(args: Array[String]): Unit = {
        resolve("S") //implicit found!  No problem
    }
}

However, if I don't know this type at compile time, then I run into issues.

def main(args: Array[String]): Unit = {
    val string = StdIn.readLine()
    resolve(string) //Can't find implicit because it doesn't know the singleton type at runtime.
}

Is there anyway I can get around this? Maybe some method that takes a String and returns the singleton type of that string?

def getSingletonType[T <: SS](string: String): T = ???

Then maybe I could do

def main(args: Array[String]): Unit = {
    val string = StdIn.readLine()
    resolve(getSingletonType(string))
}

Or is this just not possible? Maybe you can only do this sort of thing if you know all of the information at compile-time?


Solution

  • Normally implicits are resolved at compile time. But val string = StdIn.readLine() becomes known at runtime only. Principally, you can postpone implicit resolution till runtime but you'll be able to apply the results of such resolution at runtime only, not at compile time (static types etc.)

    object Entry {
      implicit val e1 = ...
      implicit val e2 = ...
    }
    
    import scala.reflect.runtime.universe._
    import scala.reflect.runtime
    import scala.tools.reflect.ToolBox
    val toolbox = ToolBox(runtime.currentMirror).mkToolBox()
    
    def resolve(s: String): Any = {
      val typ = appliedType(
        typeOf[Entry[_]].typeConstructor,
        internal.constantType(Constant(s))
      )
      val instanceTree = toolbox.inferImplicitValue(typ, silent = false)
      val instance = toolbox.eval(toolbox.untypecheck(instanceTree)).asInstanceOf[Entry[_]]
      instance.value
    }
    
    resolve("S") // 3
    
    val string = StdIn.readLine()
    resolve(string)
    // 3 if you enter S
    // ABC if you enter T
    // "scala.tools.reflect.ToolBoxError: implicit search has failed" otherwise
    

    Please notice that I put implicits into the companion object of type class in order to make them available in the implicit scope and therefore in the toolbox scope. Otherwise the code should be modified slightly:

    object EntryImplicits {
      implicit val e1 = ...
      implicit val e2 = ...
    }
    
    // val instanceTree = toolbox.inferImplicitValue(typ, silent = false)
    //   should be replaced with
    val instanceTree =
      q"""
        import path.to.EntryImplicits._
        implicitly[$typ]
      """
    

    In your code import path.to.EntryImplicits._ is import Main._.

    Load Dataset from Dynamically generated Case Class