动态方法调用Clojure宏?

时间:2009-11-10 20:29:11

标签: clojure

我正在尝试编写一个宏,它将根据给定的参数调用java setter方法。

所以,例如:

(my-macro login-as-fred {"Username" "fred" "Password" "wilma"})

可能会扩展为以下内容:

(doto (new MyClass)
  (.setUsername "fred")
  (.setPassword "wilma"))

你会如何建议解决这个问题?

具体来说,我无法找到构造setter方法名称的最佳方法,并让它将其解释为宏的符号。

4 个答案:

答案 0 :(得分:20)

关于宏的好处是你实际上不必深入研究类或类似的东西。您只需编写代码即可生成正确的s表达式

首先生成一个像(.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))

因为它是一个宏,所以它永远不必知道它被调用的东西是什么类,或者甚至是它所使用的类存在。

答案 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

不需要宏(据我所知,宏也不允许绕过反射;至少对于一般情况而言)。