我试图编写一个宏,根据给定的参数调用Java setter方法。
例如:
(my-macro login-as-fred {"Username" "fred" "Password" "wilma"})
可能会扩展成以下内容:
(doto (new MyClass)
(.setUsername "fred")
(.setPassword "wilma"))
你会如何推荐解决这个问题?
具体而言,我在构建setter方法名以及让宏将其解释为符号方面遇到了困难。
我试图编写一个宏,根据给定的参数调用Java setter方法。
例如:
(my-macro login-as-fred {"Username" "fred" "Password" "wilma"})
可能会扩展成以下内容:
(doto (new MyClass)
(.setUsername "fred")
(.setPassword "wilma"))
你会如何推荐解决这个问题?
具体而言,我在构建setter方法名以及让宏将其解释为符号方面遇到了困难。
(.setName 42)
这样的 s 表达式。(defn make-call [name val]
(list (symbol (str ".set" name) val)))
然后编写一个宏来生成表达式,并将它们插入到 doto
表达式中(~@)。
(defmacro map-set [class things]
`(doto ~class ~@(map make-call things))
因为它是一个宏,所以它永远不必知道调用它的事物所属的类,甚至不必知道将要在哪个类上使用它。
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"))
syntax-quote
,那么很容易失去对宏内部工作原理的了解,并且认为syntax-quote
是唯一的宏构建工具。有一件事情我会建议避免使用,那就是(map (fn [[x y]] ...) coll)
;这可以更清晰地表达为(for [[x y] coll] ...)
。 - amalloy(list \
first some-arg)`。 - amalloy(list 'first some-arg)
很容易,而不太可能键入(~'first ~some-arg)
而不是(first ~some-arg)
。在这种情况下,甚至更短。如果你感觉舒服,可以使用list
方法。但我会建议在95%的情况下避免使用它。(想象一下正确的位置有一些反引号。我不知道评论格式。) - kotarak我相信是Arthur Ulfeldt曾经发布过一篇几乎正确的答案,但现在已被删除。
以下是一个可行版本:
(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>
你必须硬着头皮使用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
据我所知,不需要使用宏(至少对于一般情况而言,宏也无法规避反射)。请注意不要删除HTML标签。
doto
吗?这样做会对类对象本身进行操作,而不是该类的实例。 - Brian Carper