2011-05-13 49 views
9

在「Clojure的喜悅」一書中,提供了defprotocol作爲表達問題的解決方案 - 「希望爲現有的一組現有抽象方法實現具體類而不必更改定義的代碼。「Clojure defprotocol作爲表達問題的解決方案

給出的示例如下:

(defprotocol Concatenatable 
    (cat [this other])) 

(extend-type String 
    Concatenatable 
    (cat [this other] 
    (.concat this other))) 

(cat "House" " of Leaves") 
;=> "House of Leaves" 

(extend-type java.util.List 
    Concatenatable 
    (cat [this other] 
    (concat this other))) 

(cat [1 2 3] [4 5 6]) 
;=> (1 2 3 4 5 6) 

有人建議,這是不可能在Java等語言,但它是如何比下面有什麼不同?

public class Util { 
    public static String cat(final String first, 
          final String second) { 
    return first.concat(second); 
    } 

    public static <T> List<T> cat(final List<T> first, 
           final List<T> second) { 
    final List<T> list = new List<T>(first); 
    list.addAll(second); 
    return list; 
    } 
} 

畢竟,兩者都類似地使用:

(cat "House" " of Leaves") 
Util.cat("House", " of Leaves"); 

Clojure的功能cat一個方法StringList類,而是一個獨立的功能即重載以接受StringList參數。

雖然我非常喜歡Clojure,但我不明白這個構造的優越性。

+2

它們都以相似的方式操作。不同的是,在Java中,參與者集合是由'Util'的作者定義的,而在Clojure情況下,參與者集合由'Concatenatable'的用戶定義。 – fogus 2011-05-13 13:04:57

回答

21

好的。您很容易發佈這個Java庫,並且每個人都會下載它。非常好,我想讓我自己的TVCommercial類型可連接,以便我可以將它發送到可操作在可連接對象上的庫的某些位。

但我不能,因爲你打電話Util.cat(obj1, obj2),它沒有TVCommercial超載。我無法擴展您的代碼來處理我的類型,因爲我不擁有您的代碼。

您可以定義Concatenable作爲一個接口來解決這個問題:

interface Concatenable { 
    Concatenable cat(Concatenable other); 
} 

但現在我不能寫一類是既Concatenable和...我不知道,一個AnimalHandler,用於處理cat小號。 Clojure的協議通過分散調度功能和實現來解決這兩個問題:它們遍佈整個地方,而不是在某個單一位置。在Java中,你可以選擇之間:

  • 把你的所有類型的派遣到一個單一的開關/箱或重載方法
  • 定義接口強制的方法具有特定名稱

Clojure的基本上沒有了後者,但因爲它使用命名空間名稱,所以與其他協議沒有衝突的危險認爲cat是一個很好的函數名稱。

+0

您提到的名稱碰撞問題與表達式問題並不真正密切相關。但是定義接口'Concatenble'是問題的一部分,因爲它鎖定了只有一個相關的操作:'cat'。添加新操作需要再次打開'Concatenable',或者可能定義參與類型必須實現的其他接口。 – seh 2011-05-13 12:28:22

+0

@seh我不認爲我遵循。如果我們想要增加可連接目標必須支持的操作次數,那麼我們必須重新定義協議,就像我們重新定義接口一樣。我認爲Clojure允許這樣做,如果你調用一個未實現的協議函數,它會引發一個未經檢查的異常,但這只是對問題的討論,而不是解決問題。 – amalloy 2011-05-13 17:16:31

+0

是的,當針對沒有該函數的擴展的類型的實例調用協議函數時,Clojure拋出'IllegalArgumentException'。不過,與協議的契約,或許在精神上比字母更重要的是,* whole *協議將被定義爲它所擴展的類型。否則,在一個協議中將這些功能捆綁在一起有什麼意義?與CL的通用函數相比,它只能通過相同的包成員關係將它們聚集在一起,但是沒有辦法表示一組函數應該全部一起定義。 – seh 2011-05-13 18:16:44

2

每當一個新的類型出現時你想申請的cat函數,你需要「重新」你Util類,並添加方法重載爲新的目標類型。

表達式問題試圖避免這種需求,使得現有類型不受新操作定義的干擾,並且現有實現的操作不會受到希望參與這些操作的新類型的干擾。此處顯示的Clojure協議示例並不符合第一個目標,因爲向已發佈協議添加新功能要求協議已擴展到的所有類型都爲該新方法定義了一個實現。

+0

*爲已發佈協議添加新功能要求協議已擴展至的所有類型都定義了該新方法的實現。* - 我想知道 - 您是否嘗試過?如果您執行的OP代碼,然後下面,你會看到,是不正確的: (defprotocol Concatenatable (貓[本等]) (轉[這])) 用戶=>(貓「House」)「 」House of Leaves「 不需要重新定義。 – fogus 2011-05-13 12:55:43

+0

不,我沒有嘗試,但是當您針對字符串調用新的'rev'函數時會發生什麼?是不是這種情況下'String'只能*部分*實現協議'Concatenatable'?也許我應該寫道,增加一個協議不能包含像抽象類一樣的默認實現,因此之前定義的類型擴展仍然是* defined *但現在是不完整的,用這種類型調用新函數將產生一個'IllegalArgumentException'。 – seh 2011-05-13 18:12:45