Получение типа функции метода из экземпляра MethodMirror в Scala

Предположим, у меня есть экземпляр MethodMirror, созданный для определенного метода объекта. По полям зеркала я могу легко получить доступ к возвращаемому типу и параметрам метода. Но на самом деле мне нужно получить тип, который этот метод будет иметь как функцию.

Вот пример игрушечного кода, который поможет мне объяснить, чего я хочу достичь. Я использую Скала 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)
  }
}

Я хочу создать экземпляр Type из methodMirror, который будет представлять функцию. Для этого примера это должно быть (String, String) => Int. Я бы предпочел решение, которое не слишком зависит от конкретных классов FunctionX Scala.


person Mr 525    schedule 16.08.2015    source источник
comment
Что вы имеете в виду, говоря, что я бы предпочел решение, которое не слишком зависит от конкретных классов FunctionX Scala? (String, String) => Int — это просто другое имя для Function2[String, String, Int], на самом деле это одно и то же (и поэтому ни одно из них не является более конкретным, чем другое).   -  person Régis Jean-Gilles    schedule 20.08.2015
comment
@RégisJean-Gilles Я думаю, это означает, что можно построить тип функции из типа метода, используя universe.appliedType и передав ему Function2, типы аргументов и тип возвращаемого значения, но OP хочет более общий способ расширения eta для объектов типа .   -  person Kolmar    schedule 20.08.2015
comment
Ах да, вы правы, наверное, это то, что он имел в виду. Спасибо.   -  person Régis Jean-Gilles    schedule 20.08.2015
comment
На самом деле, я искал любой метод решения моей проблемы. Общий способ обработки этого, конечно, более элегантен, поскольку я не могу предсказать, какой будет арность исходного метода. Я пытался использовать universe.appliedType перед написанием этого поста, но у меня были некоторые загадочные ошибки, и я пришел к выводу, что использую неправильный инструмент (скудная документация по отражению Scala как-то связана с этим).   -  person Mr 525    schedule 20.08.2015


Ответы (2)


Метод getEtaExpandedMethodType ниже делает то, что вы просили, и даже обрабатывает методы с несколькими списками параметров.

С другой стороны, он не обрабатывает общие методы. Например, def method[T](x: T) = 123 при расширении эта создает функцию типа 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)

РЕПЛ-тест:

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
person Régis Jean-Gilles    schedule 20.08.2015
comment
Спасибо за столь универсальное решение. Кстати, для меня важно иметь эти маркеры аргументов типа - я читаю их из объявления метода и использую позже, чтобы определить, можно ли использовать этот метод в определенном контексте. Но эта вещь compat._ может устареть в будущих версиях Scala. Прежде чем я прочитал ваш ответ, я написал свой собственный код (после того, как узнал, что appliedType все-таки был правильным инструментом), который я публикую ниже. - person Mr 525; 20.08.2015
comment
Я не был очень рад использовать compat._, но просто не смог найти новую альтернативу менее чем за одну минуту, поэтому я просто оставил ее как есть (документация по рефлексии уже очень скудна, но когда она устаревает, становится настоящей проблемой найти то, что тебе нужно). Я заменил typeRef на appliedType в своем коде, работает как шарм. - person Régis Jean-Gilles; 21.08.2015
comment
Самое смешное, что теперь я понимаю, что Колмар уже указал мне на appliedType в своем комментарии. Дох, глупый я. - person Régis Jean-Gilles; 21.08.2015

Вот, пожалуй, самое простое решение с использованием 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)
}
person Mr 525    schedule 20.08.2015
comment
Функциональность arityToFunType уже предоставляется стандартной библиотекой через definitions.FunctionClass. Это устраняет основную боль в вашем коде. - person Régis Jean-Gilles; 21.08.2015
comment
Верно. Спасибо, что указали на это. Еще одна скрытая полезная утилита отражения Scala. Я улучшил свое решение, которое теперь стало довольно коротким. - person Mr 525; 21.08.2015