(Обновлено со вторым подходом - см ниже второго горизонтального положения - а также некоторые пояснительные замечания повторно: первый.)
Интересно, если это может быть шагом в правильном направлении:
(defmacro reify-from-maps [iface implicits-map emit-map & ms]
`(reify ~iface
[email protected](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 [email protected]))
NB. что макрос atom-bean
передает фактическое время компиляции значение от emit-atom-g&ss
по reify-from-maps
. Как только форма atom-bean
скомпилирована, любые последующие изменения в emit-atom-g&ss
не влияют на поведение созданного объекта.
Пример macroexpansion из РЕПЛ (с некоторыми разрывами строк и отступами добавлен для ясности):
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
; аргументы функций - это карта «implicits» (в основном любая и вся информация, которая должна быть доступна для всех определений методов в форме reify-from-maps
, как и имя атома в данном конкретном случае), за которым следует аргумент «спецификатор магического метода» в форме reify-from-maps
. Как уже упоминалось выше, reify-from-maps
необходимо увидеть фактическое ключевое слово -> карту функций, а не его символическое имя; поэтому он действительно полезен только с буквальными картами, внутри других макросов или с помощью eval
.
Стандартные определения методов все еще могут быть включены и будут обрабатываться как в обычной форме reify
, при условии, что ключи, соответствующие их именам, не встречаются в emit-map
. Испускающие функции должны возвращать значения (например, векторы) определений методов в формате, ожидаемом reify
: таким образом случай с несколькими определениями методов, возвращенный для одного «спецификатора магического метода», относительно прост. Если аргумент iface
был заменен на ifaces
и ~iface
с [email protected]
в корпусе reify-from-maps
, для реализации можно было бы указать несколько интерфейсов.
Вот другой подход, возможно, легче рассуждать о:
(defn compile-atom-bean-converter [ifaces get-set-map]
(eval
(let [asym (gensym)]
`(fn [~asym]
(reify [email protected]
[email protected](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
Я думал, что я помню, как Чаусер демонстрировал в основном такое же использование 'eval' на SO и, конечно же, [здесь это] (http://stackoverflow.com/questions/3748559/clojure-creating-new-instance-from -string класса имя/3752276 # 3752276). Рассматриваемый сценарий отличается, но его объяснение связанного с производительностью компромисса очень уместно для нынешней ситуации. –
Ничего себе. Спасибо за отличный ответ. –