我正在尝试实现一个巨大的Java接口,其中包含大量(~50)个getter和setter方法(有些方法的名称不规范)。我认为使用宏来减少代码量会很好。因此,不是
(def data (atom {:x nil}))
(reify HugeInterface
(getX [this] (:x @data))
(setX [this v] (swap! data assoc :x v))) 我想要能够写作
(def data (atom {:x nil}))
(reify HugeInterface
(set-and-get getX setX :x))这个set-and-get宏(或类似的东西)可能吗?我一直没能让它工作。
发布于 2010-12-14 09:29:02
(使用第二种方法更新--参见下面的第二条水平规则--以及一些解释性注释re:第一条。)
我想知道这是不是朝着正确的方向迈出了一步:
(defmacro reify-from-maps [iface implicits-map emit-map & ms]
`(reify ~iface
~@(apply concat
(for [[mname & args :as m] ms]
(if-let [emit ((keyword mname) emit-map)]
(apply emit implicits-map args)
[m])))))
(def emit-atom-g&ss
{:set-and-get (fn [implicits-map gname sname k]
[`(~gname [~'this] (~k @~(:atom-name implicits-map)))
`(~sname [~'this ~'v]
(swap! ~(:atom-name implicits-map) assoc ~k ~'v))])})
(defmacro atom-bean [iface a & ms]
`(reify-from-maps ~iface {:atom-name ~a} ~emit-atom-g&ss ~@ms))注意:atom-bean宏将emit-atom-g&ss的实际编译时值传递给reify-from-maps。编译特定的atom-bean表单后,对emit-atom-g&ss的任何后续更改都不会对创建的对象的行为产生任何影响。
一个来自REPL的宏扩展示例(为了清晰起见,添加了一些换行和缩进):
user> (-> '(atom-bean HugeInterface data
(set-and-get setX getX :x))
macroexpand-1
macroexpand-1)
(clojure.core/reify HugeInterface
(setX [this] (:x (clojure.core/deref data)))
(getX [this v] (clojure.core/swap! data clojure.core/assoc :x v)))因为atom-bean是一个扩展到另一个宏调用的宏,所以两个macroexpand-1是必需的。macroexpand不会特别有用,因为它会一直扩展到对reify*的调用,这是reify背后的实现细节。
这里的想法是,您可以提供一个类似于上面的emit-atom-g&ss的emit-map,以关键字为关键字,其名称(以符号形式)将在reify-from-maps调用中触发魔术方法生成。魔术是由存储为给定emit-map中的函数的函数执行的;函数的参数是“隐式”(基本上是reify-from-maps形式中的所有方法定义都可以访问的任何和所有信息,如本例中原子的名称)的映射,后面是为reify-from-maps形式的“魔术方法说明符”提供的任何参数。如上所述,reify-from-maps需要看到实际的关键字->函数映射,而不是它的符号名称;因此,只有在其他宏内或在eval的帮助下,它才能真正用于文字映射。
如果在emit-map中没有出现与其名称匹配的键,则仍然可以包含普通的方法定义,并将其视为常规的reify表单。emit函数必须以reify期望的格式返回方法定义的可选参数(例如向量):这样,为一个“魔术方法说明符”返回多个方法定义的情况就相对简单了。如果在ifaces的主体中将iface参数替换为ifaces,将~iface替换为~@ifaces,则可以为实现指定多个接口。
这是另一种方法,可能更容易理解:
(defn compile-atom-bean-converter [ifaces get-set-map]
(eval
(let [asym (gensym)]
`(fn [~asym]
(reify ~@ifaces
~@(apply concat
(for [[k [g s]] get-set-map]
[`(~g [~'this] (~k @~asym))
`(~s [~'this ~'v]
(swap! ~asym assoc ~k ~'v))])))))))这将在运行时调用编译器,这有点昂贵,但对于要实现的每组接口,只需执行一次。结果是一个函数,它接受一个原子作为参数,并在原子周围实现一个包装器,按照get-set-map参数中的指定使用getter和setter实现给定的接口。(以这种方式编写,这比以前的方法更不灵活,但上面的大部分代码可以在这里重用。)
下面是一个示例接口和一个getter/setter映射:
(definterface IFunky
(getFoo [])
(^void setFoo [v])
(getFunkyBar [])
(^void setWeirdBar [v]))
(def gsm
'{:foo [getFoo setFoo]
:bar [getFunkyBar setWeirdBar]})和一些REPL交互:
user> (def data {:foo 1 :bar 2})
#'user/data
user> (def atom-bean-converter (compile-atom-bean-converter '[IFunky] gsm))
#'user/atom-bean-converter
user> (def atom-bean (atom-bean-converter data))
#'user/atom-bean
user> (.setFoo data-bean 3)
nil
user> (.getFoo atom-bean)
3
user> (.getFunkyBar data-bean)
2
user> (.setWeirdBar data-bean 5)
nil
user> (.getFunkyBar data-bean)
5发布于 2010-12-14 18:17:40
重点是reify本身是一个宏,它在你自己的set-and-get宏之前扩展,所以set-and-get方法不起作用。因此,除了reify内部的宏之外,你还需要一个“外部”的宏来生成reify。
发布于 2014-07-19 03:45:46
由于诀窍是在reify看到主体之前对其进行扩展,因此更通用的解决方案可能是这样的:
(defmacro reify+ [& body]
`(reify ~@(map macroexpand-1 body)))https://stackoverflow.com/questions/4434902
复制相似问题