2010-12-14 29 views
6

Tôi đang cố triển khai một giao diện Java lớn với nhiều phương thức getter và setter (~ 50) (một số có tên không đều). Tôi nghĩ rằng nó sẽ là tốt đẹp để sử dụng một vĩ mô để giảm số lượng mã. Vì vậy, thay vìSử dụng macro đinh bấm để tự động tạo getters và setters bên trong cuộc gọi reify

(def data (atom {:x nil})) 
(reify HugeInterface 
    (getX [this] (:x @data)) 
    (setX [this v] (swap! data assoc :x v))) 

Tôi muốn để có thể viết

(def data (atom {:x nil})) 
(reify HugeInterface 
    (set-and-get getX setX :x)) 

Đây có phải là thiết lập và lấy vĩ mô (hoặc một cái gì đó tương tự) có thể? Tôi đã không thể làm cho nó hoạt động được.

Trả lời

9

(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~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 
+0

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. –

+0

Chà. Cảm ơn bạn vì câu trả lời tuyệt vời. –

4

Điểm chính là bản thân macro được mở rộng trước macro cài đặt và nhận của riêng bạn - vì vậy cách tiếp cận đặt và lấy không hoạt động. Vì vậy, thay vì một macro bên trong bên trong reify, bạn cần một macro trên "bên ngoài" mà tạo ra các reify, quá.

+0

Đó là một điểm tốt. Cảm ơn. –

0

Bạn cũng có thể thử force your macro to expand first:

(ns qqq (:use clojure.walk)) 
(defmacro expand-first [the-set & code] `(do [email protected](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))) 
0

Kể từ khi Bí quyết là để mở rộng cơ thể trước khi cụ thể hóa nhìn thấy nó, một giải pháp tổng quát hơn có thể là một cái gì đó dọc theo những dòng:

(defmacro reify+ [& body] 
    `(reify [email protected](map macroexpand-1 body))) 
Các vấn đề liên quan