首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用clojure宏在reify调用中自动创建getter和setter

使用clojure宏在reify调用中自动创建getter和setter
EN

Stack Overflow用户
提问于 2010-12-14 08:45:38
回答 4查看 1.3K关注 0票数 6

我正在尝试实现一个巨大的Java接口,其中包含大量(~50)个getter和setter方法(有些方法的名称不规范)。我认为使用宏来减少代码量会很好。因此,不是

代码语言:javascript
复制
(def data (atom {:x nil}))
(reify HugeInterface
  (getX [this] (:x @data))
  (setX [this v] (swap! data assoc :x v))) 

我想要能够写作

代码语言:javascript
复制
(def data (atom {:x nil}))
(reify HugeInterface
  (set-and-get getX setX :x))

这个set-and-get宏(或类似的东西)可能吗?我一直没能让它工作。

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2010-12-14 09:29:02

(使用第二种方法更新--参见下面的第二条水平规则--以及一些解释性注释re:第一条。)

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

代码语言:javascript
复制
(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的宏扩展示例(为了清晰起见,添加了一些换行和缩进):

代码语言:javascript
复制
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&ssemit-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,则可以为实现指定多个接口。

这是另一种方法,可能更容易理解:

代码语言:javascript
复制
(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映射:

代码语言:javascript
复制
(definterface IFunky
  (getFoo [])
  (^void setFoo [v])
  (getFunkyBar [])
  (^void setWeirdBar [v]))

(def gsm
  '{:foo [getFoo setFoo]
    :bar [getFunkyBar setWeirdBar]})

和一些REPL交互:

代码语言:javascript
复制
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
票数 9
EN

Stack Overflow用户

发布于 2010-12-14 18:17:40

重点是reify本身是一个宏,它在你自己的set-and-get宏之前扩展,所以set-and-get方法不起作用。因此,除了reify内部的宏之外,你还需要一个“外部”的宏来生成reify。

票数 4
EN

Stack Overflow用户

发布于 2014-07-19 03:45:46

由于诀窍是在reify看到主体之前对其进行扩展,因此更通用的解决方案可能是这样的:

代码语言:javascript
复制
(defmacro reify+ [& body]
  `(reify ~@(map macroexpand-1 body)))
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/4434902

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档