【问题标题】:Getting method's function type from the MethodMirror instance in Scala从 Scala 中的 MethodMirror 实例获取方法的函数类型
【发布时间】:2015-08-16 23:28:50
【问题描述】:

假设我有一个为某个对象的某个方法创建的MethodMirror 实例。通过镜像的字段,我可以轻松访问方法的返回类型和参数。但我实际上需要获取此方法作为函数的类型。

这是一个玩具代码示例,可以帮助我解释我想要实现的目标。我正在使用 Scala 2.11.6。

import scala.reflect.runtime.universe._

object ForStackOverflow {
  object Obj {
    def method(x:String, y:String):Int = 0
    def expectedRetType():((String, String) => Int) = ???
  }

  def main(args: Array[String]) {
    val mirror:Mirror = runtimeMirror(getClass.getClassLoader)
    val instanceMirror = mirror.reflect(Obj)

    val methodSymbol:MethodSymbol = instanceMirror.symbol.toType.decl(TermName("method")).asMethod
    val methodMirror = instanceMirror.reflectMethod(methodSymbol)

    println(methodMirror.symbol.returnType)
    println(methodMirror.symbol.paramLists(0).map { x => x.info.resultType }.mkString(", "))

    val expectedSymbol:MethodSymbol = instanceMirror.symbol.toType.decl(TermName("expectedRetType")).asMethod
    println("I would like to produce from a 'methodMirror' this: "+expectedSymbol.returnType)
  }
}

我想从methodMirror 生成Type 实例,它代表一个函数。对于这个例子,它应该是(String, String) => Int。我更喜欢不太依赖具体 Scala 的 FunctionX 类的解决方案。

【问题讨论】:

  • “我更喜欢一个不太依赖具体 Scala 的 FunctionX 类的解决方案”是什么意思? (String, String) => Int 只是 Function2[String, String, Int] 的另一个名称,它们实际上是一回事(因此,它们中没有一个比另一个更“具体”)。
  • @RégisJean-Gilles 我认为这意味着,可以使用 universe.appliedType 从方法类型构造函数类型并将其传递给 Function2,参数类型和返回类型,但是 OP想要一种更通用的方法来对类型对象进行 eta 扩展。
  • 啊,是的,你是对的,这可能就是他的意思。谢谢。
  • 实际上,我一直在寻找 any 方法来解决我的问题。处理这个的通用方法当然更优雅,因为我无法预测原始方法的数量。在写这篇文章之前,我尝试使用universe.appliedType,但我遇到了一些神秘的错误,我得出的结论是我使用了错误的工具(很少有 Scala 的反射文档与它有关)。

标签: scala reflection scala-2.11 scala-reflect


【解决方案1】:

下面的 getEtaExpandedMethodType 方法可以满足您的要求,甚至可以处理具有多个参数列表的方法。

另一方面,它不处理泛型方法。例如def method[T](x: T) = 123,当eta-expanded时,创建一个Any => Int类型的函数,但是getEtaExpandedMethodType会报告T => Int,这不仅不正确而且根本没有意义(T在这方面没有意义上下文)。

def getEtaExpandedMethodType(methodSymbol: MethodSymbol): Type = {
  val typ = methodSymbol.typeSignature
  def paramType(paramSymbol: Symbol): Type = {
    // TODO: handle the case where paramSymbol denotes a type parameter
    paramSymbol.typeSignatureIn(typ)
  }

  def rec(paramLists: List[List[Symbol]]): Type = {
    paramLists match {
      case Nil => methodSymbol.returnType
      case params :: otherParams =>
        val functionClassSymbol = definitions.FunctionClass(params.length)
        appliedType(functionClassSymbol, params.map(paramType) :+ rec(otherParams))
    }
  }
  if (methodSymbol.paramLists.isEmpty) { // No arg method
    appliedType(definitions.FunctionClass(0), List(methodSymbol.returnType))
  } else {
    rec(methodSymbol.paramLists)
  }
}
def getEtaExpandedMethodType(methodMirror: MethodMirror): Type = getEtaExpandedMethodType(methodMirror.symbol)

REPL 测试:

scala> val mirror: Mirror = runtimeMirror(getClass.getClassLoader)
mirror: reflect.runtime.universe.Mirror = ...

scala> val instanceMirror = mirror.reflect(Obj)
instanceMirror: reflect.runtime.universe.InstanceMirror = instance mirror for Obj$@21b6e507

scala> val tpe = instanceMirror.symbol.toType
tpe: reflect.runtime.universe.Type = Obj.type

scala> getEtaExpandedMethodType(tpe.decl(TermName("method1")).asMethod)
res28: reflect.runtime.universe.Type = (String, String) => scala.Int

scala> getEtaExpandedMethodType(tpe.decl(TermName("method2")).asMethod)
res29: reflect.runtime.universe.Type = () => String

scala> getEtaExpandedMethodType(tpe.decl(TermName("method3")).asMethod)
res30: reflect.runtime.universe.Type = () => scala.Long

scala> getEtaExpandedMethodType(tpe.decl(TermName("method4")).asMethod)
res31: reflect.runtime.universe.Type = String => (scala.Float => scala.Double)

scala> getEtaExpandedMethodType(tpe.decl(TermName("method5")).asMethod)
res32: reflect.runtime.universe.Type = T => scala.Int

scala> getEtaExpandedMethodType(tpe.decl(TermName("method6")).asMethod)
res33: reflect.runtime.universe.Type = T => scala.Int

【讨论】:

  • 感谢您提供如此通用的解决方案。顺便说一句,拥有这些类型参数标记对我来说很重要 - 我从方法的声明中读取它们并稍后使用来确定是否可以在特定上下文中使用此方法。但是这个compat._ 的东西在未来的Scala 版本中可能会过时。在阅读您的答案之前,我编写了自己的代码(在我了解到 appliedType 毕竟是正确的工具之后),我将其发布在下面。
  • 我不太喜欢使用compat._,但在不到一分钟的时间里找不到新的替代品,所以我就让它保持原样(反射文档已经很稀疏了,但是当它出来的时候迄今为止,找到您需要的东西真的很痛苦)。我在我的代码中用appliedType 替换了typeRef,就像一个魅力。
  • 有趣的是,我现在意识到 Kolmar 在他的评论中已经将我指向了appliedType。哎呀,我傻了。
【解决方案2】:

这可能是使用universe.appliedType 的最直接的解决方案。它在多个参数列表的情况下不起作用。我发布此内容是为了展示解决此问题的另一种方法。

def getEtaExpandedMethodType2(methodSymbol: MethodSymbol): Type  = {
  val typesList = methodSymbol.info.paramLists(0).map(x => x.typeSignature) :+ methodSymbol.returnType
  val arity = methodSymbol.paramLists(0).size
  universe.appliedType(definitions.FunctionClass(arity), typesList)
}

【讨论】:

  • 标准库已经通过definitions.FunctionClass 提供了arityToFunType 功能。这消除了代码中的主要痛点。
  • 对。感谢您指出这一点。另一个隐藏的有用 Scala 反射实用程序。我改进了我的解决方案,现在变得很短。
猜你喜欢
  • 1970-01-01
  • 2016-04-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-20
  • 2014-09-25
  • 1970-01-01
  • 2017-09-18
相关资源
最近更新 更多