(Cập nhật ngày với một cách tiếp cận thứ hai - xem dưới đây hard rule thứ hai - cũng như một số nhận xét giải thích lại: cái đầu tiên.)
Tôi tự hỏi liệu đây có phải một bước theo đúng hướng:
(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. rằng macro atom-bean
chuyển giá trị biên dịch thực tế giá trị của emit-atom-g&ss
vào số reify-from-maps
. Khi một biểu mẫu atom-bean
cụ thể được biên dịch, mọi thay đổi tiếp theo đối với emit-atom-g&ss
sẽ không ảnh hưởng đến hành vi của đối tượng đã tạo.
Một ví dụ macroexpansion từ REPL (với một số ngắt dòng và thụt đầu dòng thêm cho rõ ràng):
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)))
Hai macroexpand-1
s là cần thiết, bởi vì atom-bean
là một macro mà mở rộng đến một cuộc gọi vĩ mô hơn nữa. macroexpand
sẽ không đặc biệt hữu ích, vì nó sẽ mở rộng tất cả các cách này để gọi đến reify*
, chi tiết triển khai sau reify
.
Ý tưởng ở đây là bạn có thể cung cấp emit-map
như emit-atom-g&ss
ở trên, được khóa bằng từ khóa có tên (ở dạng biểu tượng) sẽ kích hoạt tạo phương pháp ma thuật trong các cuộc gọi reify-from-maps
. Phép thuật được thực hiện bởi các hàm được lưu trữ như các hàm trong emit-map
đã cho; các đối số cho các hàm là một bản đồ của "implicits" (về cơ bản bất kỳ và tất cả các thông tin có thể truy cập được tới tất cả các định nghĩa phương thức theo dạng reify-from-maps
, như tên của nguyên tử trong trường hợp cụ thể này). "magic specifier specifier" ở dạng reify-from-maps
. Như đã đề cập ở trên, reify-from-maps
cần xem từ khóa thực tế -> bản đồ hàm, không phải là tên biểu tượng của nó; vì vậy, nó chỉ thực sự có thể sử dụng được với các bản đồ theo nghĩa đen, bên trong các macro khác hoặc với sự trợ giúp của eval
.
Định nghĩa phương thức bình thường vẫn có thể được bao gồm và sẽ được xử lý dưới dạng reify
thông thường, các phím được cung cấp khớp với tên của chúng không xuất hiện trong emit-map
. Các hàm phát ra phải trả về các hàm seqables (ví dụ: vectơ) của các định nghĩa phương thức theo định dạng được mong đợi bởi reify
: theo cách này, trường hợp với nhiều định nghĩa phương thức được trả về cho một "trình chỉ định phương thức ma thuật" tương đối đơn giản. Nếu đối số iface
được thay thế bằng ifaces
và ~iface
với [email protected]
trong cơ thể reify-from-maps
', nhiều giao diện có thể được chỉ định để triển khai.
Dưới đây là cách tiếp cận khác, có thể dễ dàng hơn để suy luận về:
(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))])))))))
này kêu gọi các trình biên dịch tại thời gian chạy, mà là hơi đắt tiền, nhưng chỉ cần được thực hiện một lần cho mỗi bộ giao diện được thực hiện. Kết quả là một hàm lấy một nguyên tử làm đối số và sửa đổi một trình bao bọc xung quanh nguyên tử thực hiện các giao diện đã cho với các getters và setters như được chỉ rõ trong đối số get-set-map
. (Viết theo cách này, đây là ít linh hoạt hơn so với phương pháp trước đó, nhưng hầu hết các mã trên có thể được tái sử dụng ở đây.)
Dưới đây là một giao diện mẫu và một getter/đồ setter:
(definterface IFunky
(getFoo [])
(^void setFoo [v])
(getFunkyBar [])
(^void setWeirdBar [v]))
(def gsm
'{:foo [getFoo setFoo]
:bar [getFunkyBar setWeirdBar]})
Và một số REPL tương tác:
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
Tôi nghĩ rằng tôi nhớ Chouser thể hiện về cơ bản cùng một loại sử dụng 'eval' trên SO và, chắc chắn đủ, [ở đây là] (http://stackoverflow.com/questions/3748559/clojure-creating-new-instance-từ -string-class-name/3752276 # 3752276). Kịch bản được xem xét có sự khác biệt, nhưng lời giải thích của ông về sự tham gia thương mại hiệu quả là rất phù hợp với tình hình hiện tại. –
Chà. Cảm ơn bạn vì câu trả lời tuyệt vời. –