Clojure vars и статические методы Java - PullRequest
6 голосов
/ 17 мая 2010

Я несколько дней изучаю Clojure и у меня возникают некоторые проблемы с прорезыванием зубов, поэтому я прошу совета.

Я пытаюсь сохранить класс Java в переменной Clojure и вызывать его статические методы, но он не работает.

Пример:

user=> (. java.lang.reflect.Modifier isPrivate 1)
false
user=> (def jmod java.lang.reflect.Modifier)
#'user/jmod
user=> (. jmod isPrivate 1)
java.lang.IllegalArgumentException: No matching method found: isPrivate for class java.lang.Class (NO_SOURCE_FILE:0)
    at clojure.lang.Compiler.eval(Compiler.java:4543)

Из этого исключения похоже, что среда выполнения ожидает, что переменная будет содержать объект, поэтому она вызывает .getClass (), чтобы получить класс, и ищет метод, используя отражение. В этом случае переменная уже содержит класс, поэтому .getClass() возвращает java.lang.Class, и поиск метода явно завершается неудачей.

Есть ли способ обойти это, кроме написания моего собственного макроса?

В общем случае я хотел бы иметь объект или класс в переменной и вызывать для него соответствующие методы - типизацию утки как для статических, так и для экземпляров методов.

В этом конкретном случае я просто хотел бы более короткое имя для java.lang.reflect.Modifier, если хотите, псевдоним. Я знаю о import, но ищу что-то более общее, например, псевдоним пространства имен Clojure, но для классов Java. Есть ли другие механизмы для этого?

Edit:

Может быть, я просто запутался в соглашениях о вызовах здесь. Я думал, что модель Lisp (и, соответственно, Clojure) должна была оценить все аргументы и вызвать первый элемент в списке как функцию.

В этом случае (= jmod java.lang.reflect.Modifier) возвращает true, а (.getName jmod) и (.getName java.lang.reflect.Modifier) оба возвращают одну и ту же строку.

Таким образом, переменная и имя класса однозначно оценивают одно и то же, но их все равно нельзя вызывать одинаково. Что здесь происходит?

Редактировать 2

Отвечая на мой второй вопрос (что здесь происходит), доктор Clojure говорит, что

Если первый операнд является символом, разрешает имя класса, доступ считается статическим членом именованного класса ... В противном случае это предполагается, что является экземпляром

http://clojure.org/java_interop в разделе "Форма Dot"

«Преобразование в имя класса», очевидно, не то же самое, что «вычисление чего-то, что разрешается в имя класса», поэтому то, что я пытаюсь сделать здесь, не поддерживается специальной формой точка.

Ответы [ 3 ]

6 голосов
/ 18 мая 2010

(Обновление: я подготовил что-то, что могло бы быть приемлемым в качестве решения ... Оригинальный ответ остается ниже горизонтального правила к концу сообщения.)


Я только что написал макрос, чтобы включить это:

(adapter-ns java.lang.reflect.Modifier jmod)
; => nil
(jmod/isStatic 1)
; => false
(jmod/isStatic 8)
; => true

Идея заключается в создании универсального пространства имен, импортировании статики данного класса как Vars в это пространство имен, а затем в псевдониме пространства имен к некоторому полезному имени. Свернуто, но это работает! : -)

Код выглядит так:

(defmacro import-all-statics
  "code stolen from clojure.contrib.import-static/import-static"
  [c]
  (let [the-class (. Class forName (str c))
        static? (fn [x]
                  (. java.lang.reflect.Modifier
                     (isStatic (. x (getModifiers)))))
        statics (fn [array]
                  (set (map (memfn getName)
                            (filter static? array))))
        all-fields (statics (. the-class (getFields)))
        all-methods (statics (. the-class (getMethods)))
        import-field (fn [name]
                       (list 'def (symbol name)
                             (list '. c (symbol name))))
        import-method (fn [name]
                        (list 'defmacro (symbol name)
                              '[& args]
                              (list 'list ''. (list 'quote c)
                                    (list 'apply 'list
                                          (list 'quote (symbol name))
                                          'args))))]
    `(do ~@(map import-field all-fields)
         ~@(map import-method all-methods))))

(defmacro adapter-ns [c n]
  (let [ias (symbol (-> (resolve 'import-all-statics) .ns .name name)
                    "import-all-statics")]
    `(let [ns-sym# (gensym (str "adapter_" ~n))]
       (create-ns 'ns-sym#)
       (with-ns 'ns-sym#
         (clojure.core/refer-clojure)
         (~ias ~c))
       (alias '~n 'ns-sym#))))

Выше показано, как Var хранит макрос import-all-statics в некоторой запутанной форме (что, однако, гарантированно сработает, если макрос виден из текущего пространства имен). Если вы знаете, в каком пространстве имен оно будет найдено, оригинальная версия, которую я написал, является более простой заменой:

(defmacro adapter-ns [c n]
  `(let [ns-sym# (gensym (str "adapter_" ~n))]
     (create-ns 'ns-sym#)
     (with-ns 'ns-sym#
       (clojure.core/refer-clojure)
       ;; NB. the "user" namespace is mentioned below;
       ;; change as appropriate
       (user/import-all-statics ~c))
     (alias '~n 'ns-sym#)))

(Оригинальный ответ ниже.)

Я понимаю, что это не совсем то, что вы просите, но, возможно, clojure.contrib.import-static/import-static будет вам полезно:

(use 'clojure.contrib.import-static)

(import-static clojure.lang.reflect.Modifier isPrivate)

(isPrivate 1)
; => false
(isPrivate 2)
; => true

Обратите внимание, что import-static импортирует статические методы как макросы.

1 голос
/ 18 мая 2010

Вы успешно сохраняете класс в jmod, но isPrivate является статическим методом java.lang.reflect.Modifier, а не java.lang.Class.

Вы можете сделать это с помощью отражения:

(. (. jmod getMethod "isPrivate" (into-array [Integer/TYPE])) 
  invoke nil (into-array [1]))
0 голосов
/ 18 мая 2010

Вот макрос, вдохновленный двумя предыдущими ответами, который обрабатывает статические методы для имен классов и переменных с именами классов, а также методы экземпляров для объектов:

(defmacro jcall [obj & args]
  (let [ref (if (and (symbol? obj) 
                  (instance? Class (eval obj)))
              (eval obj)
              obj) ]
    `(. ~ref ~@args)))

Как относительный новичок в макросах,Сложно было правильно оценить порядок оценки.

Для других новичков: параметр obj для макроса передается без оценки, и нам нужно форсировать оценку vars, чтобы имя var расширялось до имени класса, которое оно содержит.Для этого нам нужно явное значение eval, вне фактического тела макроса.

Проверка того, является ли obj символом, предназначена для ограничения оценки переменными.Проверка того, содержит ли переменная класс, позволяет пропустить оценку не-классов, а затем работает и для объектов и методов экземпляра.

Пример использования:

   ;; explicit class name, static method
user=> (jcall java.lang.reflect.Modifier isPrivate 1) 
false
  ;; class name from var, static method
user=> (jcall jmod isPrivate 1) 
false

  ;; works for objects and instance methods too
user=> (jcall (Object.) toString) 
"java.lang.Object@3acca07b"
 ;; even with the object in a variable
user=> (def myobj (Object.))
#'user/myobj
user=> (jcall myobj toString)
"java.lang.Object@4ccbb612"

  ;; but not for instance methods on classes
user=> (jcall Object toString) 
java.lang.NoSuchFieldException: toString (NO_SOURCE_FILE:747)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...