无论如何,在 Scala 中,是否可以从更通用的类型中获取 Singleton 类型?

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

我有一种情况,我试图对单例类型使用隐式解析。如果我在编译时知道单例类型,这将非常有效:

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
    }
}

但是,如果我在编译时不知道这种类型,那么我 运行 就会遇到问题。

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.
}

我有办法解决这个问题吗?也许某些方法采用 String 和 returns 该字符串的单例类型?

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

那也许我可以做

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

或者这是不可能的?如果你在编译时知道所有信息,也许你只能做这种事情?

通常隐式在编译时解析。但是 val string = StdIn.readLine() 仅在运行时才知道。原则上,您可以将隐式解析推迟到运行时,但您只能在运行时应用此类解析的结果,而不是在编译时(静态类型等)

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

请注意,我将隐式放入 class 类型的伴随对象中,以便使它们在隐式范围内可用,因此在工具箱范围内可用。否则应稍微修改代码:

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]
  """

在您的代码中 import path.to.EntryImplicits._import Main._

如果您知道 Entry 在编译时的所有可能实现 - 这只有在密封时才有可能 - 那么您可以使用宏来创建 map/partial 函数 String -> Entry[_].

由于这是对扩展开放的,恐怕最多一些运行时反射必须扫描整个类路径以找到所有可能的实现。

但即便如此,您也必须以某种方式将此 String 文字嵌入到每个实现中,因为 JVM 字节码对单例类型和实现之间的映射一无所知——只有 Scala 编译器知道。然后使用它来查找在所有实现中是否有一个(并且恰好是一个)与您的值匹配 - 如果在隐含的情况下,如果在同一范围内同时有两个,编译将失败,但您可以有多个只要不在同一范围内一起出现即可实施。运行时反射将是全局的,因此无法避免冲突。

所以不,没有好的解决方案可以使 compile-time 调度动态化。您可以自己创建这样的调度,例如自己写一个 Map[String, Entry[_]] 并使用 get 函数来处理丢失的图片。