使用Clojure宏自动在Reify调用中自动创建Getters和Setter
题
我正在尝试使用许多(〜50)Getter和setter方法(有些具有不规则名称)来实现巨大的Java界面。我认为使用宏来减少代码量会很不错。所以而不是
(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))
这是可能的设置宏(或类似的东西)吗?我无法使它起作用。
解决方案
(使用第二种方法进行更新 - 请参见下面的第二个水平规则 - 以及一些解释性评论回复:第一个。)
我想知道这是否可能是朝着正确方向迈出的一步:
(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))
NB。那是 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)))
二 macroexpand-1
S是必要的,因为 atom-bean
是一个宏,可以扩展到进一步的宏调用。 macroexpand
不会特别有用,因为它将一直将其扩展到呼叫 reify*
, ,背后的实现细节 reify
.
这里的想法是您可以提供 emit-map
喜欢 emit-atom-g&ss
上面,由关键字键入其名称(符号形式)将触发魔术方法生成的关键字。 reify-from-maps
呼叫。魔术是由存储在给定函数的函数执行的 emit-map
;函数的参数是“隐性”的映射(基本上是所有和所有信息都应在一个方法中访问的所有信息 reify-from-maps
形式,就像在这种情况下的原子的名称一样),然后在任何参数中给出任何参数。 reify-from-maps
形式。正如刚才提到的, reify-from-maps
需要查看实际关键字 - >函数映射,而不是其符号名称;因此,它只能在字面地图,其他宏内部或借助于 eval
.
正常方法定义仍然可以包括在内,并将被视为常规 reify
表格,提供匹配其名称的键不会出现在 emit-map
. 。发射函数必须返回按预期的格式定义的seqables(例如向量) reify
: :这样,带有多个方法定义的情况是一个“魔术方法指定符”相对简单。如果是 iface
争论被取代 ifaces
和 ~iface
和 ~@ifaces
在 reify-from-maps
'主体,可以指定多个接口进行实施。
这是另一种方法,可能更容易理解:
(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地图:
(definterface IFunky
(getFoo [])
(^void setFoo [v])
(getFunkyBar [])
(^void setWeirdBar [v]))
(def gsm
'{:foo [getFoo setFoo]
:bar [getFunkyBar setWeirdBar]})
还有一些重复互动:
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
其他提示
重点是将其作为宏本身,它在您自己的设置和宏观之前进行了扩展 - 因此,设置和获取方法不起作用。因此,您还需要在“外部”上产生重新限制的内部宏内部宏。
你也可以尝试 迫使您的宏首先扩展:
(ns qqq (:use clojure.walk))
(defmacro expand-first [the-set & code] `(do ~@(prewalk #(if (and (list? %) (contains? the-set (first %))) (macroexpand-all %) %) code)))
(defmacro setter [setterf kw] `(~setterf [~'this ~'v] (swap! ~'data assoc ~kw ~'v)))
(defmacro getter [getterf kw] `(~getterf [~'this] (~kw @~'data)))
(expand-first #{setter getter}
(reify HugeInterface
(getter getX :x)
(setter setX :x)))
因为诀窍是扩大身体 重新 看到它,一个更通用的解决方案可能是这些方案:
(defmacro reify+ [& body]
`(reify ~@(map macroexpand-1 body)))