Используйте макрос Clojure, чтобы автоматически создавать GetTers и Setters внутри вызова RIFY

StackOverflow https://stackoverflow.com/questions/4434902

  •  09-10-2019
  •  | 
  •  

Вопрос

Я пытаюсь осуществить огромный интерфейс Java с многочисленными (~ 50) методами Getter и Setter (некоторые с нерегулярными именами). Я думал, что было бы приятно использовать макрос, чтобы уменьшить количество кода. Так вместо

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

Возможен ли этот Macro (или что-то похожее)? Я не смог сделать это работать.

Это было полезно?

Решение

(Обновлено со вторым подходом - см. Ниже второго горизонтального правила - а также некоторые пояснительные замечания Re: первый.)


Интересно, может ли это быть шагом в правильном направлении:

(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 не влияют на поведение созданного объекта.

Пример Macroexpansion от REFL (с некоторыми линиями разрывов и вдавливания для ясности):

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 форма, как название атома в этом конкретном случае), за которым следует любые аргументы на «спецификатор MAGIC MESS) в reify-from-maps форма. Как указано выше, reify-from-maps необходимо увидеть фактическое ключевое слово -> карта функции, а не его символическое имя; Итак, это действительно только полезно с буквальными картами, внутри других макросов или с помощью eval.

Определения нормального метода все еще могут быть включены и будут рассматриваться как в регулярной основе reify форма, предоставленные ключи, соответствующие их имена, не происходят в emit-map. Отказ Функции EMIT должны вернуть SEQables (например, векторы) определений метода в формате, ожидаемом reify: Таким образом, случай с несколькими определениями метода, возвращаемыми для одного «спецификатора Magic Method», является относительно простым. Если то 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))])))))))

Это призывает к компилятору во время выполнения, что несколько дороги, но нужно только сделать только один раз на набор интерфейсов, которые будут реализованы. Результатом является функция, которая принимает атом в качестве аргумента и обозначает обертку вокруг атома, реализующего данные интерфейсы с Getter и Devices, как указано в 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