0

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!

John Dorian
  • 1,884
  • 1
  • 19
  • 29
  • 1
    I think it would be useful to go into how you imagine using such a feature. When people start needing `eval` to do this kind of metaprogramming, there is usually some alternate solution that would achieve the same goal, but it would require a different approach. e.g., instead of defining a var and then separately creating functions based on that var, you can sometimes let a variable at global scope, and within that `let` define macros that have access to that definition via lexical scoping. – amalloy Nov 29 '18 at 19:56
  • You don't need read-string for that, the 'clojure.core/symbol' function can be used to convert a string to a symbol – ChrisBlom Dec 01 '18 at 07:41

2 Answers2

0

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.

amalloy
  • 89,153
  • 8
  • 140
  • 205
  • You're right, posted a version with `eval` that does work. It seems like that is at least one way to bridge the runtime compiletime gap. Is there a way to do it without eval, or maybe just limiting the parts that need to be `eval`d? – John Dorian Nov 29 '18 at 05:15
0

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
KobbyPemson
  • 2,519
  • 1
  • 18
  • 33