Macros 使用clojure宏在具体化调用中自动创建getter和setter

Macros 使用clojure宏在具体化调用中自动创建getter和setter,macros,clojure,reify,Macros,Clojure,Reify,我正试图用大量(~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 (s

我正试图用大量(~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))
这个set和get宏(或类似的东西)可能吗?我没能让它工作。

(更新了第二种方法——见下面的第二条水平规则——以及一些关于第一条的解释性评论。)


我想知道这是否是朝着正确方向迈出的一步:

(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))
注意。
atombean
宏将
emit-atom-g&ss
的实际编译时值传递给
reaifyfrommaps
。一旦编译了特定的
atombean
表单,对
emit-atom-g&ss
的任何后续更改都不会影响所创建对象的行为

REPL的宏扩展示例(为清晰起见,添加了一些换行符和缩进):

两个
macroexpand-1
s是必需的,因为
atombean
是一个可以扩展到另一个宏调用的宏
macroexpand
不会特别有用,因为它会将其一直扩展到调用
reify*
,这是
reify
后面的实现细节

这里的想法是,您可以提供一个
emit-map
,如上面的
emit-atom-g&ss
,由关键字键入,这些关键字的名称(以符号形式)将在
从映射具体化调用中触发魔法方法生成。通过存储为给定
emit map
中函数的函数来执行魔术;函数的参数是“implicits”的映射(基本上是所有方法定义都可以从映射
reify from maps形式访问的任何和所有信息,就像在这个特殊情况下原子的名称),然后是给定给“magic method specifier”的任何参数在
从地图具体化
表单中。如上所述,
reifyfrommaps
需要查看实际的关键字->函数映射,而不是它的符号名称;因此,它只有在文字映射、其他宏内部或在
eval
的帮助下才真正可用

正常方法定义仍然可以包括在内,并将被视为常规的
具体化
表单,前提是与它们的名称匹配的键不会出现在
发出映射
中。emit函数必须以
reify
所期望的格式返回方法定义的序列表(例如向量):这样,为一个“magic method specifier”返回多个方法定义的情况相对简单。如果将
iface
参数替换为
ifaces
,并将
ifaces
替换为
~-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

关键是具体化为一个宏本身,它在您自己的set和get宏之前展开,所以set和get方法不起作用。因此,除了在具体化中使用内部宏之外,您还需要在“外部”中使用一个宏来生成具体化。

您还可以尝试:


由于诀窍是在具体化之前展开身体,因此一个更通用的解决方案可以是以下几点:

(defmacro reify+ [& body]
  `(reify ~@(map macroexpand-1 body)))

我想我记得Chouser演示了基本上相同的
eval
用法,果然。考虑中的情况有所不同,但他对所涉及的绩效权衡的解释与当前情况非常相关。
(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)))