(Aktualizacja z drugim podejściu - patrz poniżej drugiej linii poziomej - jak również kilka objaśniających re: pierwszy z nich.)
Zastanawiam się, czy to może być krokiem we właściwym kierunku:
(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. że makro atom-bean
przechodzi rzeczywistą wartość czasu kompilacji z . Po skompilowaniu określonego formularza atom-bean
wszelkie kolejne zmiany w emit-atom-g&ss
nie mają wpływu na zachowanie utworzonego obiektu.
Przykład macroexpansion z REPL (z przerwami linii i wgłębienia dodaje się dla jasności)
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)))
Dwa macroexpand-1
S są konieczne, ponieważ atom-bean
jest makro, który rozszerza się do dalszego makro połączenia. macroexpand
nie byłby szczególnie przydatny, ponieważ rozszerzyłby to aż do połączenia z reify*
, szczegółami implementacji za reify
.
Pomysł polega na tym, że można dostarczyć powyższy kod , z kluczami utworzonymi przez słowa kluczowe, których nazwy (w formie symbolicznej) wywołają generowanie magicznej metody w wywołaniach reify-from-maps
. Magia jest wykonywana przez funkcje przechowywane jako funkcje w danym emit-map
; argumenty funkcji są mapą "implicits" (w zasadzie wszelkie informacje, które powinny być dostępne dla wszystkich definicji metod w formularzu reify-from-maps
, jak nazwa atomu w tym konkretnym przypadku), a następnie niezależnie od tego, które argumenty zostały podane do "specyfikator metody magicznej" w formularzu reify-from-maps
. Jak wspomniano powyżej, reify-from-maps
musi zobaczyć rzeczywiste słowo kluczowe -> mapę funkcji, a nie jej symboliczną nazwę; tak naprawdę można go używać tylko w mapach literalnych, w innych makrach lub przy pomocy eval
.
Normalne definicje metod mogą być nadal dołączane i będą traktowane jak zwykły formularz reify
, pod warunkiem, że klucze odpowiadające ich nazwom nie występują w emit-map
. Funkcje emitujące muszą zwracać seqables (np. Wektory) definicji metod w formacie oczekiwanym przez reify
: w ten sposób przypadek z wieloma definicjami metod zwróconymi dla jednego "specyfikatora metody magicznej" jest stosunkowo prosty. Jeśli argument iface
został zamieniony na ifaces
i ~iface
z ciałem [email protected]
w ciele reify-from-maps
, można było podać wiele interfejsów do implementacji.
Oto inne podejście, być może łatwiej rozumieć o:
(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))])))))))
Ten wzywa kompilator w czasie wykonywania, który jest nieco droższe, ale tylko należy zrobić raz na zestaw interfejsów być wdrożony. Wynikiem jest funkcja, która przyjmuje atom jako argument i rewiduje wrapper wokół atomu implementującego dane interfejsy z elementami pobierającymi i ustawiającymi, jak określono w argumencie get-set-map
. (Napisany w ten sposób, to jest mniej elastyczny niż w poprzednim podejściu, ale większość kodu powyżej mogą być ponownie wykorzystane tutaj).
Oto interfejs próbki i getter/setter mapa:
(definterface IFunky
(getFoo [])
(^void setFoo [v])
(getFunkyBar [])
(^void setWeirdBar [v]))
(def gsm
'{:foo [getFoo setFoo]
:bar [getFunkyBar setWeirdBar]})
A niektóre rEPL interakcje:
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
Pomyślałem, że przypomniałem sobie, że Chouser demonstruje w zasadzie to samo użycie 'eval' na SO i, oczywiście, [tutaj jest] (http://stackoverflow.com/questions/3748559/clojure-creating-new-instance- from -string-class-name/3752276 # 3752276). Rozważany scenariusz jest inny, ale jego wyjaśnienie dotyczące kompromisu w zakresie wydajności jest bardzo istotne dla obecnej sytuacji. –
Wow. Dziękuję za doskonałą odpowiedź. –