Clojure evaluating string variable as a symbol, is this use of read-string okay?

268 Views Asked by At

I can use memfn to create a clojure function that invokes a java function.

(macroexpand '(memfn startsWith prefix))
=> (fn* ([target2780 prefix] (. target2780 (startsWith prefix))))
((memfn startsWith prefix) "abc" "a")
=> true

memfn requires that the function name be a symbol. I'm wondering if I can write a macro to invoke an arbitrary method whose name is provided as a string. That is, I'd like to be able to invoke the following:

(def fn-name "startsWith")
=> #'user/fn-name
(macroexpand '(memfn' fn-name "prefix"))
=> (fn* ([target2780 prefix] (. target2780 (startsWith prefix))))
((memfn fn-name "prefix") "abc" "a")
=> true

The only way I can think to do this involves using read-string.

(defmacro memfn' [fn-name arg-name]
  `(memfn ~(read-string fn-name) ~arg-name))

Edit: A version using read-string and eval that actually works the way I want it to.

(defn memfn' [fn-name arg-name]
  (eval (read-string (str "(memfn " fn-name " " arg-name ")"))))

Am I missing a fundamental macro building tool to take the string that a symbol references and turn it into a literal without potentially executing code, as read-string might?

Thanks for any ideas!

2

There are 2 best solutions below

1
amalloy On BEST ANSWER

There's no way to do this, with or without read-string. Your proposed solution doesn't work. The distinction you're really trying to make is not between string and symbol, but between runtime data and compile-time literals. Macros do not evaluate the arguments they receive, so even if fn-name is the name of a var whose value is "startsWith", memfn (or your memfn' macro) will only ever see fn-name.

0
KobbyPemson On

If you are interested in calling java methods only then you can rely on java.lang.reflect.Method and its invoke method.

Something like this should work for parameterless methods and would not require a macro.

(defn memfn' [m]
  (fn [o] (.invoke (.getMethod (-> o .getClass) m nil) o nil)))

((memfn' "length") "clojure")
;=>7