Есть ли простой (идиоматический) способ преобразовать java.lang.reflect.Method в функцию Scala?

Могу ли я получить метод через отражение, каким-то образом объединить его с целевым объектом и вернуть его как нечто похожее на функцию в Scala (то есть вы можете вызвать его, используя круглые скобки)? Список аргументов переменный. Это не обязательно должна быть «первоклассная» функция (я обновил вопрос), просто вызов функции синтаксического вида, например f (аргументы).

Моя попытка пока выглядит примерно так (технически это псевдокод, просто чтобы не загромождать пост дополнительными определениями):

class method_ref(o: AnyRef, m: java.lang.reflect.Method) {
    def apply(args: Any*): some_return_type = {
        var oa: Array[Object] = args.toArray.map { _.asInstanceOf[Object] }
        println("calling: " + m.toString + " with: " + oa.length)

        m.invoke(o, oa: _*) match  {
            case x: some_return_type => x;
            case u => throw new Exception("unknown result" + u);
        }
    }
}

С помощью вышесказанного я смог обойти ошибки компилятора, но теперь у меня есть исключение во время выполнения:

Caused by: java.lang.IllegalArgumentException: argument type mismatch

Пример использования выглядит примерно так:

  var f = ... some expression returning method_ref ...;
  ...
  var y = f(x) // looks like a function, doesn't it?

ОБНОВЛЕНИЕ

Изменение args: Any * на args: AnyRef * фактически устранило мою проблему во время выполнения, поэтому описанный выше подход (с исправлением) отлично подходит для того, что я пытался выполнить. Я думаю, что здесь я столкнулся с более общей проблемой с varargs.


person Alex R    schedule 18.04.2010    source источник
comment
О ... Я упустил из виду легкое. Нет, нет. Раньше было, но это было экспериментально и было отброшено.   -  person Daniel C. Sobral    schedule 18.04.2010
comment
Вы всегда можете объяснить, где возникла ошибка, поскольку метод method_ref, опубликованный вами, не компилируется.   -  person Daniel C. Sobral    schedule 18.04.2010
comment
На самом деле похоже, что изменение Any * на AnyRef * устранило мою проблему, хотя я не понимаю, почему.   -  person Alex R    schedule 19.04.2010


Ответы (2)


Если вы не ищете общий invoke, который принимает имя метода - а, скорее, вы хотите зафиксировать конкретный метод для определенного объекта - и вы не хотите слишком углубляться в манифесты и тому подобное, я думаю следующее - достойное решение:

class MethodFunc[T <: AnyRef](o: Object, m: reflect.Method, tc: Class[T]) {
  def apply(oa: Any*): T = {
    val result = m.invoke(o, oa.map(_.asInstanceOf[AnyRef]): _*)
    if (result.getClass == tc) result.asInstanceOf[T]
    else throw new IllegalArgumentException("Unexpected result " + result)
  }
}

Посмотрим на это в действии:

val s = "Hi there, friend"
val m = s.getClass.getMethods.find(m => {
  m.getName == "substring" && m.getParameterTypes.length == 2
}).get
val mf = new MethodFunc(s,m,classOf[String])

scala> mf(3,8)
res10: String = there

Сложная часть - получить правильный тип возвращаемого значения. Здесь вам остается предоставить его. Например, если вы укажете classOf[CharSequence], произойдет сбой, потому что это неправильный класс. (Для этого лучше подходят манифесты, но вы просили о простом ... хотя я думаю, что «простой в использовании» обычно лучше, чем «просто кодировать функциональность».)

person Rex Kerr    schedule 18.04.2010
comment
В моем случае тип возвращаемого значения фиксирован, так что дополнительных сложностей не потребовалось. Я до сих пор озадачен вещами о вараргах. Я вижу, вы добавили .asInstanceOf [AnyRef], где у меня был .asInstanceOf [Object]. - person Alex R; 19.04.2010
comment
@Alex: AnyRef - это Scala для Object. Я использую Object, чтобы обозначить, что здесь используется Java-y, и AnyRef, чтобы обозначить, что это здесь Scala-y. - person Rex Kerr; 19.04.2010

Конечно. Вот код, который я написал, добавляя интерфейс к функции. Это не совсем то, что вам нужно, но я думаю, что его можно адаптировать с небольшими изменениями. Самое сложное изменение - это invoke, где вам нужно будет заменить вызываемый метод на метод, полученный посредством отражения. Кроме того, вы должны позаботиться о том, чтобы полученный метод, который вы обрабатываете, был apply. Кроме того, вместо f вы должны использовать целевой объект. Вероятно, это должно выглядеть примерно так:

  def invoke(proxy: AnyRef, method: Method, args: Array[AnyRef]) = method match {
    case m if /* m is apply */ => target.getClass().getMethod("name", /* parameter type */).invoke(target, args: _*)
    case _ => /* ??? */
  }

Во всяком случае, вот код:

import java.lang.reflect.{Proxy, InvocationHandler, Method}

class Handler[T, R](f: Function1[T, R])(implicit fm: Manifest[Function1[T, R]]) extends InvocationHandler {
  def invoke(proxy: AnyRef, method: Method, args: Array[AnyRef]) = method.invoke(f, args: _*)
  def withInterface[I](implicit m: Manifest[I]) = {
    require(m <:< manifest[Function1[T, R]] && m.erasure.isInterface)
    Proxy.newProxyInstance(m.erasure.getClassLoader(), Array(m.erasure), this).asInstanceOf[I]
  }
}

object Handler {
  def apply[T, R](f: Function1[T, R])(implicit fm: Manifest[Function1[T, R]]) = new Handler(f)
}

И используйте это так:

trait CostFunction extends Function1[String, Int]
Handler { x: String => x.length } withInterface manifest[CostFunction]

Использование здесь "manifest" помогает с синтаксисом. Вы могли бы написать это так:

Handler({ x: String => x.length }).withInterface[CostFunction] // or
Handler((_: String).length).withInterface[CostFunction]

Можно также отказаться от манифеста и использовать вместо него classOf с небольшими изменениями.

person Daniel C. Sobral    schedule 18.04.2010
comment
Ой. Должно быть что-нибудь попроще. См. Мой обновленный вопрос с тем, что я пробовал до сих пор, что на данный момент компилируется, но не работает во время выполнения. - person Alex R; 18.04.2010