使用clojure宏在reify调用中自动创建getter和setter [英] Use a clojure macro to automatically create getters and setters inside a reify call

查看:192
本文介绍了使用clojure宏在reify调用中自动创建getter和setter的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图实现一个巨大的Java接口与众多(〜50)getter和setter方法(一些与不规则的名称)。我认为这将是很好,使用宏减少代码量。因此,不是

 (def data(atom {:x nil}))
(reify HugeInterface
(getX [this](:x @data))
(setX [this v](swap!data assoc:xv)))

我想能写

 (def data(atom {:x nil}))
(reify HugeInterface
(set-and-get getX setX:x))


b $ b

这个set-and-get宏(或类似的东西)可能吗?

解决方案

(更新了第二种方法 - 第二条横线规则 - 以及一些解释性说明:第一条。)






如果这可能是正确方向的一步:

 (defmacro reify-from-maps [iface implicits-map emit-map & ms] 
`(reify〜iface
〜@(apply concat
(for [[mname& args:as m] ms]
(if-let [emit ((关键字mname)emit-map)]
(apply emit implicits-map args)
[m]))))bbb
(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 c $ c>到 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:xv )))

两个 macroexpand-1 是必要的,因为 atom-bean 是一个宏,扩展到另一个宏调用。 宏展开将不会特别有用,因为它会将此扩展为对 reify * 的调用, reify 。



这里的想法是你可以提供 emit- map 如上所述的 emit-atom-g& ss ,其关键字的名称(符号形式)将触发 reify-from-maps 调用。魔法由存储为给定 emit-map 中的函数的函数执行;函数的参数是implicits的映射(基本上是 reify-from-maps 形式的所有方法定义都应该可以访问的任何和所有信息,例如在这种情况下,原子的名称),然后是在 reify-from-maps 表单中给魔法方法说明符的任何参数。如上所述, reify-from-maps 需要查看实际的关键字 - >函数映射,而不是它的符号名称;所以,它只真正可用于字面地图,其他宏或帮助 eval



正常方法定义仍然可以包含,并且将被视为常规 reify 表单中处理,只要匹配其名称的键不会出现在 emit-map 。 emit函数必须以 reify :期望的格式返回方法定义的seqables(例如向量):以这种方式,返回一个魔法方法说明符的多个方法定义的情况比较简单。如果 iface 参数替换为 ifaces 〜iface 〜@ ifaces reify-from-maps '体中,可以指定多个接口来实现。 >




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

 (defn compile-atom-bean-converter [ifaces get-set-map] 
(eval
(let [asym(gensym)]
` 〜asym]
(reify〜@ ifaces
〜@(apply concat
(for [[k [gs]] get-set-map]
[` 〜this(〜k @〜asym))
`(〜s this〜'v]
(swap!〜asym assoc〜k〜 )))

这在运行时调用编译器,这有点贵,但只需要对每组要实现的接口执行一次。结果是一个函数,它接受一个原子作为参数,并使用在 get-set-map 参数中指定的getter和setter来实现给定接口的原子。 (这样写,这比以前的方法灵活,但是上面的大多数代码可以在这里重用。)



这里有一个示例接口和getter / setter map:

 (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


I am trying to implement a huge Java interface with numerous (~50) getter and setter methods (some with irregular names). I thought it would be nice to use a macro to reduce the amount of code. So instead of

(def data (atom {:x nil}))
(reify HugeInterface
  (getX [this] (:x @data))
  (setX [this v] (swap! data assoc :x v))) 

I want to be able to write

(def data (atom {:x nil}))
(reify HugeInterface
  (set-and-get getX setX :x))

Is this set-and-get macro (or something similar) possible? I haven't been able to make it work.

解决方案

(Updated with a second approach -- see below the second horizontal rule -- as well as some explanatory remarks re: the first one.)


I wonder if this might be a step in the right direction:

(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. that the atom-bean macro passes the actual compile-time value of emit-atom-g&ss on to reify-from-maps. Once a particular atom-bean form is compiled, any subsequent changes to emit-atom-g&ss have no effect on the behaviour of the created object.

An example macroexpansion from the REPL (with some line breaks and indentation added for clarity):

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)))

Two macroexpand-1s are necessary, because atom-bean is a macro which expands to a further macro call. macroexpand would not be particularly useful, as it would expand this all the way to a call to reify*, the implementation detail behind reify.

The idea here is that you can supply an emit-map like emit-atom-g&ss above, keyed by keywords whose names (in symbolic form) will trigger magic method generation in reify-from-maps calls. The magic is performed by the functions stored as functions in the given emit-map; the arguments to the functions are a map of "implicits" (basically any and all information which should be accessible to all method definitions in a reify-from-maps form, like the name of the atom in this particular case) followed by whichever arguments were given to the "magic method specifier" in the reify-from-maps form. As mentioned above, reify-from-maps needs to see an actual keyword -> function map, not its symbolic name; so, it's only really usable with literal maps, inside other macros or with help of eval.

Normal method definitions can still be included and will be treated as in a regular reify form, provided keys matching their names do not occur in the emit-map. The emit functions must return seqables (e.g. vectors) of method definitions in the format expected by reify: in this way, the case with multiple method definitions returned for one "magic method specifier" is relatively simple. If the iface argument were replaced with ifaces and ~iface with ~@ifaces in reify-from-maps' body, multiple interfaces could be specified for implementation.


Here's another approach, possibly easier to reason about:

(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))])))))))

This calls on the compiler at runtime, which is somewhat expensive, but only needs to be done once per set of interfaces to be implemented. The result is a function which takes an atom as an argument and reifies a wrapper around the atom implementing the given interfaces with getters and setters as specified in the get-set-map argument. (Written this way, this is less flexible than the previous approach, but most of the code above could be reused here.)

Here's a sample interface and a getter/setter map:

(definterface IFunky
  (getFoo [])
  (^void setFoo [v])
  (getFunkyBar [])
  (^void setWeirdBar [v]))

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

And some REPL interactions:

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

这篇关于使用clojure宏在reify调用中自动创建getter和setter的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆