Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing static fields of a class from a non-Classname symbol

Here's an extract from a REPL session that hopefully explains what I want to achieve:

user> (Integer/parseInt "1")
1
user> (def y Integer)
#'user/y
user> (y/parseInt "1")
No such namespace: y
  [Thrown class java.lang.Exception]

How can I access static method/fields of a Java class using a non-Classname, user defined symbol?

UPDATE

The following works as expected:

user> (eval (list (symbol (.getName y) "parseInt") "1"))
1

Is there a better/more idiomatic way to achieve the same result?

like image 741
skuro Avatar asked Nov 04 '22 23:11

skuro


1 Answers

If you cannot determine the class (possibly programmatically in a macro) during compile time, you need to resort to use reflection. This would do the same thing as eval does when it tries to compile the code. See clojure.lang.Reflector/invokeStaticMethod: https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Reflector.java#L198

(import 'clojure.lang.Reflector)
;; Here, you can pass *any string you have at runtime*
(Reflector/invokeStaticMethod Integer "parseInt" (into-array ["1"]))

This can be used in arbitrary ways at runtime, since it's not a macro or a special form. For example, the name of the method could be given by the user via a GUI or though a socket at runtime.

If you have the name of the class at compile time, you can use a macro as Nicolas suggested. However, it's unnecessary to construct the code to look like (Integer/parseInt "1"), since it's just syntactic sugar for the more basic (and macro friendly) . special form: (. Integer parseInt "1").

;; Here, the method name needs to be a *string literal*
(defmacro static-call
  "Takes a Class object, a string naming a static method of it
  and invokes the static method with the name on the class with
  args as the arguments."
  [class method & args]
  `(. ~class ~(symbol method) ~@args))

However, the only "real work" this macro performs is to convert the string into a symbol. You would probably just use the . special form in an outer macro (the one that acquires the names of the methods somehow, e.g. by getting those passed as arguments, or by reading them from a var or from a configuration file).

;; Use ordinary Clojure functions to construct this
(def the-static-methods {:foo ["Integer" "parseInt"], :bar ["Long" "parseLong"]})

;; Macros have access to all previously defined values
(defmacro generate-defns []
  (cons `do (for [[name-keyword [class-string method-string]] the-static-methods]
              `(defn ~(symbol (name name-keyword)) [x#]
                 (. ~(symbol class-string) ~(symbol method-string) x#)))))

(generate-defns)
like image 152
raek Avatar answered Nov 11 '22 07:11

raek