tags:

views:

447

answers:

3

I'm a few days into learning Clojure and are having some teething problems, so I'm asking for advice.

I'm trying to store a Java class in a Clojure var and call its static methods, but it doesn't work.

Example:

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)

From the exception it looks like the runtime expects a var to hold an object, so it calls .getClass() to get the class and looks up the method using reflection. In this case the var already holds a class, so .getClass() returns java.lang.Class and the method lookup obviously fails.

Is there some way around this, other than writing my own macro?

In the general case I'd like to have either an object or a class in a varible and call the appropriate methods on it - duck typing for static methods as well as for instance methods.

In this specific case I'd just like a shorter name for java.lang.reflect.Modifier, an alias if you wish. I know about import, but looking for something more general, like the Clojure namespace alias but for Java classes. Are there other mechanisms for doing this?

Edit:

Maybe I'm just confused about the calling conventions here. I thought the Lisp (and by extension Clojure) model was to evaluate all arguments and call the first element in the list as a function.

In this case (= jmod java.lang.reflect.Modifier) returns true, and (.getName jmod) and (.getName java.lang.reflect.Modifier) both return the same string.

So the variable and the class name clearly evaluate to the same thing, but they still cannot be called in the same fashion. What's going on here?

Edit 2

Answering my second question (what is happening here), the Clojure doc says that

If the first operand is a symbol that resolves to a class name, the access is considered to be to a static member of the named class... Otherwise it is presumed to be an instance member

http://clojure.org/java_interop under "The Dot special form"

"Resolving to a class name" is apparently not the same as "evaluating to something that resolves to a class name", so what I am trying to do here is not supported by the dot special form.

+1  A: 

You are successfully storing the class in jmod, but isPrivate is a static method of java.lang.reflect.Modifier, not of java.lang.Class.

You could do this with reflection:

(. (. jmod getMethod "isPrivate" (into-array [Integer/TYPE])) 
  invoke nil (into-array [1]))
mikera
OK, that would be the "writing a macro" option, thanks for an example. But there isn't a standard way to call the static methods of Modifier in this example?
j-g-faustus
mikera
I think the underlying issue here is that what you are trying to do requires either reflection or knowledge of the class name at compile time. There's no other way to do it on the Java platform as far as I am aware.
mikera
+3  A: 

(Update: I've prepared something which might be acceptable as a solution... The original answer remains below a horizontal rule towards the end of the post.)


I've just written a macro to enable this:

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

The idea is to create a single-purpose namespace, import the statics of a given class as Vars into that namespace, then alias the namespace to some useful name. Convoluted, but it works! :-)

The code is looks like this:

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

The above looks up the Var holding the import-all-statics macro in a somewhat convoluted way (which is, however, guaranteed to work if the macro is visible from the current namespace). If you know which namespace it's going to be found in, the original version I've written is a simpler replacement:

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

(Original answer below.)

I realise that this is not really what you're asking for, but perhaps clojure.contrib.import-static/import-static will be useful to you:

(use 'clojure.contrib.import-static)

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

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

Note that import-static imports static methods as macros.

Michał Marczyk
Ah, nice. Didn't know that there was such a thing, saved me the trouble of writing my own. Thanks.
j-g-faustus
You're welcome. The answer now includes a macro I've written to match your original requirements more closely.
Michał Marczyk
Neat, I like it. Too bad I can't give more than one vote :) This looks like the best solution, but I'll leave the question open for a day or so to see if others want to chime in.
j-g-faustus
Happy to hear that. :-) Note that it's possible to have a Var called `jmod` alongside the `jmod` alias for the special-purpose namespace, should that be useful somehow. (With the Var and the adapter ns in place, both `(jmod/isStatic 8)` and `(.getMethods jmod)` would work.)
Michał Marczyk
Ah, I forgot to point out that the above includes `user/import-all-statics`... That should of course be changed to pick up `import-all-statics` from the correct namespace (or the macro could be rewritten in a smarter way to take care of that -- perhaps I'll do it later).
Michał Marczyk
Done. With luck, it should be pretty robust now.
Michał Marczyk
Actually I think I originally meant to use `"adapter."` (and not `"adapter_"`) as the initial part of the gensym's name, so as not to create a single-segment namespace (as those can be problematic).
Michał Marczyk
A: 

Here is a macro inspired by the two previous answers that handles static methods on class names and variables with class names as well as instance methods on objects:

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

As a relative newbie on macros, the tricky part was getting the evaluation order right.

For other newbies: The obj parameter to the macro is passed in with no evaluation, and we need to force the evaluation of vars so the var name expands into the class name it holds. We need an explicit eval for that, outside the actual macro body.

The test for whether obj is a symbol is there to restrict the evaluation to variables. The test for whether the variable contains a class is there to skip evaluation of non-classes, then it works for objects and instance methods too.

Example use:

   ;; 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)
j-g-faustus
This won't work reliably. Eg. try storing the class in a let local instead of a Var. The Right Way to go is using reflection.
kotarak
OK, thanks for the warning.
j-g-faustus