принимая имена методов Java как функцию arg в clojure - PullRequest
4 голосов
/ 12 ноября 2009

All

Я хочу создать функцию, которая принимает символ, представляющий метод Java, и применяет его к некоторому объекту:

(user=> (defn f [m] (. "foo" (m)))

Когда я выполняю это, я получаю результат, сильно отличающийся от того, что я ожидаю

user=> (f 'getClass)
java.lang.IllegalArgumentException: No matching method found: m for class java.lang.String (NO_SOURCE_FILE:0)

2 вопроса:

1> почему символ m называется вторым аргументом '.' функция вместо значения, связанного с м?

2> как бы я на самом деле делал то, что хочу?

Ответы [ 3 ]

12 голосов
/ 12 ноября 2009

Это не работает, потому что . является специальной формой и имеет специальные правила оценки. Обычные вызовы функций оценивают свои аргументы, но . не оценивает параметр имени метода.

Чтобы это сработало, используйте eval или измените свою функцию на макрос.

user=> (defmacro foo [o m] `(. ~o ~m))
#'user/foo
user=> (foo 123 toString)
"123"
user=> (defn bar [o m] (eval `(. ~o ~m)))
#'user/bar
user=> (bar 123 'toString)
"123"

Использование eval обычно не рекомендуется.

2 голосов
/ 12 ноября 2009

Проблема заключается в том, что вызовы метода встроены в байт-код JVM. Как утверждает Брайан, eval будет работать, потому что он компилирует код каждый раз, когда он вызывается. Однако держитесь подальше от eval. У него есть свои цели, но этот не один из них.

Лучший способ сделать то, что вы хотите, это использовать отражение. Или, если возможно, используйте макрос, который показал Брайан. Отражение может быть сделано через:

(defn f
  [m & args]
  (clojure.lang.Reflector/invokeInstanceMethod obj m (into-array Object args)))

Не проверено, хотя ...

0 голосов
/ 25 сентября 2011

Макрос, о котором Брайан упоминает, уже существует и называется memfn для "функции-члена".

user> ((memfn length) "test")
4
...