我正在尝试编写一个宏,它将根据给定的参数调用java setter方法。
所以,例如:
(my-macro login-as-fred {"Username" "fred" "Password" "wilma"})
可能会扩展为以下内容:
(doto (new MyClass)
(.setUsername "fred")
(.setPassword "wilma"))
你会如何建议解决这个问题?
具体来说,我无法找到构造setter方法名称的最佳方法,并让它将其解释为宏的符号。
答案 0 :(得分:20)
关于宏的好处是你实际上不必深入研究类或类似的东西。您只需编写代码即可生成正确的s表达式。
首先生成一个像(.setName 42)
(defn make-call [name val]
(list (symbol (str ".set" name) val)))
然后是一个宏来生成表达式并将它们插入(〜@)表达式为doto
。
(defmacro map-set [class things]
`(doto ~class ~@(map make-call things))
因为它是一个宏,所以它永远不必知道它被调用的东西是什么类,或者甚至是它所使用的类存在。
答案 1 :(得分:5)
请不要为宏构建带list
的s表达式。这将严重损害宏观的卫生。犯错很容易,很难追查。请始终使用语法报价! 虽然在这种情况下这不是问题,但养成只使用语法引用的习惯是好的!
根据地图的来源,您可能还会考虑使用关键字作为键,使其看起来更像clojure。这是我的看法:
(defmacro configure
[object options]
`(doto ~object
~@(map (fn [[property value]]
(let [property (name property)
setter (str ".set"
(.toUpperCase (subs property 0 1))
(subs property 1))]
`(~(symbol setter) ~value)))
options)))
然后可以将其用作:
user=> (macroexpand-1 '(configure (MyClass.) {:username "fred" :password "wilma"}))
(clojure.core/doto (MyClass.) (.setUsername "fred") (.setPassword "wilma"))
答案 2 :(得分:4)
某人(我相信Arthur Ulfeldt)发布的答案几乎是正确的,但现在已被删除。如果他再次发帖,请接受而不是我的。 (或接受pmf's。)这是一个有效的版本:
(defmacro set-all [obj m]
`(doto ~obj ~@(map (fn [[k v]]
(list (symbol (str ".set" k)) v))
m)))
user> (macroexpand-1 '(set-all (java.util.Date.) {"Month" 0 "Date" 1 "Year" 2009}))
(clojure.core/doto (java.util.Date.) (.setMonth 0) (.setDate 1) (.setYear 2009))
user> (set-all (java.util.Date.) {"Month" 0 "Date" 1 "Year" 2009})
#<Date Fri Jan 01 14:15:51 PST 3909>
答案 3 :(得分:2)
你必须咬紧牙关,然后像这样使用clojure.lang.Reflector/invokeInstanceMethod
:
(defn do-stuff [obj m]
(doseq [[k v] m]
(let [method-name (str "set" k)]
(clojure.lang.Reflector/invokeInstanceMethod
obj
method-name
(into-array Object [v]))))
obj)
(do-stuff (java.util.Date.) {"Month" 2}) ; use it
不需要宏(据我所知,宏也不允许绕过反射;至少对于一般情况而言)。