2011-11-04 29 views
10

Giả sử tôi có hai giao thức:Làm cách nào để bạn có thể mở rộng giao thức Clojure sang giao thức khác?

(defprotocol A 
    (f [this])) 

(defprotocol B 
    (g [x y])) 

Và tôi muốn mở rộng giao thức B cho tất cả các trường hợp có hỗ trợ giao thức A:

(extend-protocol A 
    String 
    (f [this] (.length this))) 

(extend-protocol B 
    user.A 
    (g [x y] (* (f x) (f y)))) 

Động lực chính là để tránh phải mở rộng B riêng biệt cho tất cả các lớp có thể mà A có thể được mở rộng, hoặc thậm chí đến các lớp tương lai chưa biết mà người khác có thể mở rộng A (ví dụ: nếu A là một phần của API công khai).

Tuy nhiên điều này không làm việc - bạn sẽ có được một cái gì đó như sau:

(g "abc" "abcd") 
=> #<IllegalArgumentException java.lang.IllegalArgumentException: 
No implementation of method: :g of protocol: #'user/B found for 
class: java.lang.String> 

Đây có phải là có thể ở tất cả? Nếu không, liệu có cách giải quyết hợp lý để đạt được cùng một mục tiêu không?

Trả lời

7

Dường như với tôi rằng bạn có thể thực hiện chức năng g theo điều khoản của f. Nếu đó là trường hợp bạn có tất cả các đa hình bạn cần mà không có giao thức B.

Những gì tôi có nghĩa là những điều sau đây, cho rằng f là đa hình, sau đó

(defn g [x y] 
    (* (f x) (f y))) 

mang lại một hàm g mà hỗ trợ tất cả các loại mà thực hiện giao thức A. Thông thường, khi các giao thức ở dưới cùng, các chức năng đơn giản chỉ được định nghĩa trong các hàm giao thức (hoặc trên các chức năng khác mà chính chúng sử dụng giao thức) làm cho toàn bộ không gian tên/thư viện/chương trình rất đa hình, có thể mở rộng và linh hoạt.

Thư viện trình tự là ví dụ tuyệt vời về điều này. Đơn giản hóa, có hai chức năng đa hình, firstrest. Phần còn lại của thư viện chuỗi là các hàm bình thường.

+0

Cảm ơn. Tôi nghĩ đây là cách tiếp cận tốt nhất trong trường hợp của tôi - sự tương tự với thư viện trình tự hoạt động tốt ở đây! – mikera

9

Giao thức không phải là loại và không hỗ trợ kế thừa. Một giao thức cơ bản là một bộ sưu tập có tên các định nghĩa hàm (và một cơ chế gửi đi khi các hàm đó được gọi).

Nếu bạn có nhiều loại mà tất cả xảy ra để có cùng một triển khai, bạn có thể chỉ cần gọi một hàm chung. Ngoài ra, bạn có thể tạo bản đồ phương thức và extend từng loại với bản đồ đó. Ví dụ:

 
(defprotocol P 
    (a [p]) 
    (b [p])) 

(deftype R []) 
(deftype S []) 
(deftype T []) 

(def common-P-impl 
    {:a (fn [p] :do-a) 
    :b (fn [p] :do-b)}) 

(extend R 
    P common-P-impl) 
(extend S 
    P common-P-impl) 
(extend T 
    P common-P-impl) 

Nếu bạn cung cấp thêm chi tiết về tình huống thực tế của mình, chúng tôi có thể đề xuất phương pháp chính xác.

+0

Đây cũng là giải pháp của tôi - tôi nghĩ rằng 'bản đồ' có thể được tận dụng để loại bỏ trùng lặp, ví dụ: '(bản đồ # (mở rộng% P chung-P-impl) [R S T])' – KingCode

+0

Xin lỗi, 'liềuq', hoặc một' doall' kèm theo nên được sử dụng lại: bản đồ bị lười .. – KingCode

0

Mặc dù tôi không hoàn toàn hiểu những gì bạn đang cố gắng làm, tôi tự hỏi nếu clo đa phương tiện sẽ là một giải pháp tốt hơn cho vấn đề của bạn.

1

Từ những gì tôi thấy, các giao thức thực sự có thể mở rộng các giao thức. tôi đã thực hiện một ví dụ ở đây: https://github.com/marctrem/protocol-extend-protocol-example/blob/master/src/extproto/core.clj

Trong ví dụ, chúng ta có một Animalia giao thức (cho phép các thành viên của nó làm dream) được mở rộng bởi một giao thức Erinaceinae (cho phép các thành viên của nó để go-fast).

Chúng tôi có hồ sơ Hedgehog là một phần của giao thức Erinaceinae. Ví dụ về bản ghi của chúng tôi có thể cả dreamgo-fast.

(ns extproto.core 
    (:gen-class)) 


(defprotocol Animalia (dream [this])) 

(defprotocol Erinaceinae (go-fast [this])) 

(extend-protocol Animalia 
    extproto.core.Erinaceinae 
    (dream [this] "I dream about things.")) 

(defrecord Hedgehog [lovely-name] 
    Erinaceinae 
    (go-fast [this] (format "%s the Hedgehog has got to go fast." (get this :lovely-name)))) 



(defn -main 
    [& args] 
    (let [my-hedgehog (Hedgehog. "Sanic")] 
    (println (go-fast my-hedgehog)) 
    (println (dream my-hedgehog)))) 

;1> Sanic the Hedgehog has got to go fast. 
;1> I dream about things. 
1

trong "Clojure áp dụng" có một công thức của giao thức mở rộng của giao thức

(extend-protocol TaxedCost 
    Object 
    (taxed-cost [entity store] 
    (if (satisfies? Cost entity) 
     (do (extend-protocol TaxedCost 
      (class entity) 
      (taxed-cost [entity store] 
       (* (cost entity store) (+ 1 (tax-rate store))))) 
      (taxed-cost entity store)) 
     (assert false (str "Unhandled entity: " entity))))) 

thực sự không có gì ngăn cản bạn từ giao thức đơn giản mở rộng bởi một số khác

(extend-protocol TaxedCost 
    Cost 
    (taxed-cost [entity store] 
    (* (cost entity store) (+ 1 (tax-rate store))))) 

trong khi cuốn sách nói nó không phải khả thi. Tôi đã nói chuyện với Alex Miller về điều này và anh ta nói như sau:

Nó thực sự không hoạt động trong mọi trường hợp. Giao thức tạo ra một giao diện Java và bạn có thể chắc chắn, mở rộng một giao thức cho giao diện đó. Vấn đề là không phải mọi giao thức triển khai thực hiện giao diện - chỉ các bản ghi hoặc các loại làm như vậy với một khai báo nội tuyến như (defrecord Foo [a] TheProtocol (foo ...)). Nếu bạn đang triển khai giao thức bằng cách sử dụng extend-type hoặc extend-protocol, thì những trường hợp đó sẽ KHÔNG bị bắt bởi phần mở rộng của giao diện giao thức. Vì vậy, bạn thực sự không nên làm điều này :)

Các vấn đề liên quan