我正在尝试使用许多(〜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&ssreify-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-1S是必要的,因为 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~@ifacesreify-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)))
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top