2012-04-25 9 views
17

Mam trochę kodu, który używa wielu metod i chciałbym przeładować funkcję (w tym przypadku wielofunkcyjną), tak, żebym mógł przekazać wyższą kolejność funkcji, na przykład do testowania.Czy możliwe jest przeciążenie wielu metod Clojure w arytmie?

Oto przykład:

(ns multi) 

(defn my-print [m] (println "The colour is" (:colour m))) 

(defmulti which-colour-mm (fn [m f] (:colour m))) 

(defmethod which-colour-mm :blue [m f] (f m)) 
(defmethod which-colour-mm :red [m f] (f m)) 
(defmethod which-colour-mm :default [m f] (println "Default: Neither Blue nor Red")) 

(defn which-colour 
    ([m] (which-colour-mm m my-print)) 
    ([m f] (which-colour-mm m f))) 

(which-colour {:colour :blue :object :ball}) 
(which-colour {:colour :yellow :object :ball}) 
(which-colour {:colour :blue :animal :parrot} (fn [m] (println "The " (:animal m) "is" (:colour m)))) 

Więc moja defn zapewnia arity przeciążenia, ale zastanawiam się, czy defmethod obsługuje czegoś takiego. (Chyba nie chcesz tego robić dla każdej deklaracji defmethod.)

Czy jest to najbardziej odpowiednie podejście (czy mogę powiedzieć, idiomatic), czy jest lepszy sposób?

Odpowiedz

14

To jest w porządku. Istnieje interfejs "użytkownika" i interfejs "typu" biblioteki. Mogą być identyczne, ale nie muszą.

Interfejs użytkownika to w Twoim przypadku which-colour. Interfejs "typ" to which-colour-mm (ok, nie do końca, ale tylko ze względu na argument). Użytkownik twojej biblioteki nie musi wiedzieć o multimethod.

Z drugiej strony ktoś zapewniający nowy kolor - na przykład :purple - nie musi dbać o wielowarstwową płytę główną. To jest dla niego obsługiwane w which-colour.

To jest idealnie poprawny projekt!

Ale oczywiście jest cena tag: Załóżmy, że masz kolor, który ma bardziej skuteczny sposób robienia rzeczy ... Teraz jesteś zamknięty w możliwym wolniejszym interfejsie.

Aby to trochę wyjaśnić: Załóżmy, że masz interfejs kolekcji. Podajesz funkcję - conj - która pozwala użytkownikowi dodawać elementy do kolekcji. Jest on realizowany w ten sposób:

(defn conj 
    [coll & elements] 
    (reduce conj1 coll elements)) 

conj1 jest „typ” interfejs (np funkcją multimethod lub protokół.): dodaje jeden element do kolekcji. Więc ktoś dostarczający nowy typ kolekcji musi tylko zaimplementować prosty przypadek dodania pojedynczego argumentu. A automagicznie nowy typ będzie również obsługiwał dodawanie wielu elementów.

Ale teraz załóżmy, że masz typ kolekcji, który pozwala na szybszy sposób dodawania kilku elementów niż tylko dodawanie kolejnych. Ta funkcja nie może być teraz używana.

Dzięki temu funkcja multimetru/protokołu sama w sobie jest funkcją conj. Teraz kolekcja może korzystać z szybszego sposobu. Ale każda implementacja musi zapewniać wiele elementów.

To kompromis i do Twojej decyzji. Nie ma właściwej drogi (tm). Oba można uznać za idiomatyczne. (Chociaż ja osobiście starałbym się używać tego pierwszego tak często, jak to możliwe.)

YMMV.

Edycja: Przykład metod wielorakich bez kodowania w wartości wysyłkowej.

(defmulti which-colour-mm (fn [m & args] (:colour m))) 
(defmethod which-colour-mm :blue 
    ([m] (print m)) 
    ([m f] (f m))) 
+0

Lubię to i odpowiedź ANKUR, ale ten wykorzystuje arity przeciążenia v s drugi, który używa liczby argumentów do dopasowania wartości wysyłki. Domyślam się, że warto skorzystać z metody defn, jeśli chcesz mieć tę samą domyślną funkcję dla każdej wartości wysyłki (i unikać powielania) w porównaniu do przeciążania na poziomie defmethod, jeśli chcesz mieć inną wartość domyślną dla każdej wartości wysyłkowej. –

3

Można to zrobić za pomocą multimethods jak pokazano poniżej:

(defmulti which-colour-mm (fn [m & args] [(count args) (:colour m)])) 
(defmethod which-colour-mm [0 :blue] [m] (print m)) 
(defmethod which-colour-mm [1 :blue] [m f] (f m)) 


user=> (which-colour-mm {:colour :blue :object :ball}) 
{:colour :blue, :object :ball}nil 
user=> (which-colour-mm {:colour :blue :object :ball} print) 
{:colour :blue, :object :ball}nil 
2

Zasadniczo można wysyłką na nic, ani rodzaju, ani liczby args musi być consistent..like to:

(defn- map-classes [an-object] 
    (let [cmap 
     {1 :thing 
      2 666 
      3 "yada"} 
    the-class (class an-object)] 
    (get cmap an-object the-class))) 

(defn- mk-class [& args] (map #(map-classes %) args)) 
(defmulti play-thing mk-class) 
(defmethod play-thing [:thing] [v] (= 1 v)) 
(defmethod play-thing [666] [v] (= 2 v)) 
(defmethod play-thing ["yada" String] [v x] (str x v)) 

możliwości są nieograniczone

Powiązane problemy