2010-05-17 13 views
6

Tengo algunos días para aprender Clojure y tengo algunos problemas iniciales, por lo que estoy pidiendo consejo.Clojure vars y Java métodos estáticos

Estoy tratando de almacenar una clase Java en una var Clojure y llamar a sus métodos estáticos, pero no funciona.

Ejemplo:

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) 

Desde la excepción parece que el tiempo de ejecución espera una var para sostener un objeto, por lo que llama .getClass() para obtener la clase y mira hacia arriba el método que utiliza la reflexión. En este caso, la var ya contiene una clase, por lo que .getClass() devuelve java.lang.Class y la búsqueda del método obviamente falla.

¿Hay alguna forma de evitar esto, aparte de escribir mi propia macro?

En el caso general, me gustaría tener un objeto o una clase en una variable y llamar a los métodos apropiados en él - pato de tipeo para métodos estáticos, así como, por ejemplo, métodos.

En este caso específico, me gustaría un nombre más corto para java.lang.reflect.Modifier, un alias si lo desea. Sé de import, pero busco algo más general, como el alias del espacio de nombres Clojure, pero para las clases de Java. ¿Hay otros mecanismos para hacer esto?

Editar:

Tal vez estoy confundido acerca de las convenciones de llamada aquí. Pensé que el modelo Lisp (y por extensión, Clojure) debía evaluar todos los argumentos y llamar al primer elemento de la lista como una función.

En este caso (= jmod java.lang.reflect.Modifier) devuelve verdadero, y (.getName jmod) y (.getName java.lang.reflect.Modifier) ambos devuelven la misma cadena.

Por lo que la variable y el nombre de clase claramente evalúan a la misma cosa, pero todavía no se pueden llamar de la misma manera. ¿Que está pasando aqui?

Editar 2

Responder a la segunda pregunta (lo que está sucediendo aquí), el doctor Clojure dice que

Si el primer operando es un símbolo que resuelve en un nombre de clase, el el acceso se considera que es un miembro estático de la clase nombrada ... De lo contrario, es supuestamente un miembro de instancia

http://clojure.org/java_interop en "The Dot forma especial"

"Resolución de un nombre de clase" aparentemente no es lo mismo que "la evaluación a algo que se resuelve en un nombre de clase", así que lo que estoy tratando de hacer aquí no es apoyado por la forma especial de punto.

Respuesta

6

(Actualización: Me he preparado algo que podría ser aceptable como una solución ... La respuesta original permanece por debajo de una línea horizontal hacia el final de la . posterior)


acabo de escribir una macro para activar esta:

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

La idea es crear un espacio de nombre de propósito único, importar la estática de una clase dada como Vars en ese espacio de nombre, y luego alias el espacio de nombres con algún nombre útil. ¡Convolucionado, pero funciona! :-)

El código es el siguiente aspecto:

(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 [email protected](map import-field all-fields) 
     [email protected](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#)))) 

Lo anterior busca la celebración de la macro Var import-all-statics de una manera un tanto complicado (que es, sin embargo, garantiza que funcione si la macro es visible del espacio de nombre actual). Si sabe qué espacio de nombres que va a encontrar en la versión original que he escrito es un reemplazo simple: (. Respuesta original a continuación)

(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#))) 

Soy consciente de que esta no es realmente lo que estás pidiendo, pero quizás clojure.contrib.import-static/import-static será útil para usted:

(use 'clojure.contrib.import-static) 

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

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

Tenga en cuenta que import-static importa métodos estáticos como macros.

+0

Ah, lindo. No sabía que había tal cosa, me ahorró la molestia de escribir la mía. Gracias. –

+0

De nada. La respuesta ahora incluye una macro que he escrito para que coincida con tus requisitos originales más de cerca. –

+0

Limpio, me gusta. Lástima que no puedo dar más de un voto :) Esta parece la mejor solución, pero dejaré la pregunta abierta durante un día para ver si otros quieren entrar. –

1

Está almacenando con éxito la clase en jmod, pero isPrivate es un método estático de java.lang.reflect.Modifier, no de java.lang.Class.

Usted puede hacer esto con la reflexión:

(. (. jmod getMethod "isPrivate" (into-array [Integer/TYPE])) 
    invoke nil (into-array [1])) 
+0

bien, que sería la opción "escribir una macro", gracias por ejemplo. Pero no hay una forma estándar de llamar a los métodos estáticos de Modificador en este ejemplo? –

+0

Bueno, ya encontraste (. Java.lang.reflect.Modifier isPrivate 1), lo cual está bien si conoces el nombre de la clase por adelantado. O puede usar la macro: (defmacro static-call [class & args] '(.~ class ~ @ args)) – mikera

+1

Creo que el problema subyacente aquí es que lo que estás tratando de hacer requiere reflexión o conocimiento del nombre de la clase en tiempo de compilación. No hay otra manera de hacerlo en la plataforma Java, hasta donde yo sé. – mikera

0

Aquí es una macro inspirado por las dos respuestas anteriores que maneja métodos estáticos en los nombres de clases y variables con nombres de clase, así como los métodos de instancia de objetos:

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

Como novato relativa en las macros, el complicado parte fue obtener la orden de evaluación correcta.

Para otros novatos: El parámetro obj a la macro se pasa sin evaluación, y tenemos que forzar la evaluación de vars para que el nombre var se expanda en el nombre de clase que contiene. Necesitamos una evaluación explícita para eso, fuera del cuerpo de macro real.

La prueba para ver si obj es un símbolo está allí para restringir la evaluación a las variables. La prueba de si la variable contiene una clase está allí para omitir la evaluación de las no clases, luego también funciona para objetos y métodos de instancia.

Ejemplo del uso:

;; 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) 
"[email protected]" 
;; even with the object in a variable 
user=> (def myobj (Object.)) 
#'user/myobj 
user=> (jcall myobj toString) 
"[email protected]" 

    ;; but not for instance methods on classes 
user=> (jcall Object toString) 
java.lang.NoSuchFieldException: toString (NO_SOURCE_FILE:747) 
+0

Esto no funcionará de manera confiable. P.ej. intente almacenar la clase en un let local en lugar de un Var. El buen camino a seguir es usar la reflexión. – kotarak

+0

OK, gracias por la advertencia. –