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

521 views Asked by At

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?

2

There are 2 answers

0
Dmytro Mitin On BEST ANSWER

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

3
Mateusz Kubuszok On

If you knew about all possible implementations of Entry in compile time - which would be possible only if it was sealed - then you could use a macro to create a map/partial function String -> Entry[_].

Since this is open to extending, I'm afraid at best some runtime reflection would have to scan the whole classpath to find all possible implementations.

But even then you would have to embed this String literal somehow into each implementations because JVM bytecode knows nothing about mappings between singleton types and implementations - only Scala compiler does. And then use that to find if among all implementations there is one (and exactly one) that matches your value - in case of implicits if there are two of them at once in the same scope compilation would fail, but you can have more than one implementation as long as the don't appear together in the same scope. Runtime reflection would be global so it wouldn't be able to avoid conflicts.

So no, no good solution for making this compile-time dispatch dynamic. You could create such dispatch yourself by e.g. writing a Map[String, Entry[_]] yourself and using get function to handle missing pices.