Используйте макрос clojure для автоматического создания геттеров и сеттеров внутри вызова reify - PullRequest
6 голосов
/ 14 декабря 2010

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

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

Возможен ли этот макрос типа «установить и получить» (или что-то подобное)?Я не смог заставить его работать.

Ответы [ 4 ]

9 голосов
/ 14 декабря 2010

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


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

(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&ss в 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 :x v)))

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

Это вызывает компилятор во время выполнения, что несколько дорого, но должно быть сделано только один раз для каждого набора интерфейсов, которые будут реализованы. В результате получается функция, которая принимает атом в качестве аргумента и устанавливает оболочку вокруг атома, реализуя заданные интерфейсы с геттерами и сеттерами, как указано в аргументе get-set-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
4 голосов
/ 14 декабря 2010

Дело в том, что reify сам по себе является макросом, который раскрывается перед вашим собственным макросом «установить и получить», поэтому подход «набор и получение» не работает. Таким образом, вместо внутреннего макроса внутри reify вам понадобится макрос на внешней стороне, который также генерирует reify.

0 голосов
/ 18 июля 2014

Поскольку уловка состоит в том, чтобы расширить тело до того, как reify увидит это, более общее решение может быть чем-то вроде этого:

(defmacro reify+ [& body]
  `(reify ~@(map macroexpand-1 body)))
0 голосов
/ 14 февраля 2011

Вы также можете попытаться принудительно развернуть макрос в первую очередь :

(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)))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...