2012-04-25 12 views
17

Ho un codice che utilizza metodi multipli e preferirebbe sovraccaricare la funzione (in questo caso, multi-funzione) in modo che possa passare in una funzione di ordine superiore per aiutare con il test, ad esempio.È possibile sovraccaricare i multi-metodi Clojure su arity?

Ecco l'esempio:

(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)))) 

Quindi il mio defn fornisce l'arietà sovraccarico, ma mi chiedo se defmethod supporta nulla di simile. (Immagino che non vorreste farlo per ogni dichiarazione di defmethod.)

È questo il più adatto (oserei dire, idiomatico) approccio, o c'è un modo migliore?

risposta

14

Questo va perfettamente bene. C'è l'interfaccia "utente" e l'interfaccia "tipo" di una libreria. Possono essere identici, ma non devono.

L'interfaccia "utente" è nel tuo caso which-colour. L'interfaccia di "tipo" è which-colour-mm (ok, non proprio, ma solo per il gusto dell'argomento). L'utente della tua biblioteca non ha bisogno di sapere del multimetodo.

D'altra parte, qualcuno che fornisce un nuovo colore, ad esempio :purple, non deve preoccuparsi del boilerplate multi-area. Questo è gestito per lui in which-colour.

Questo è un design perfettamente valido!

Ma ovviamente c'è un cartellino del prezzo: Supponiamo di avere un colore, che ha un modo più perfomante di fare le cose ... Ora, sei bloccato in una possibile interfaccia più lenta.

Per chiarire questo punto: Supponiamo di avere un'interfaccia di raccolta. Fornisci una funzione - conj - che consente all'utente di aggiungere elementi alla raccolta. È implementato in questo modo:

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

conj1 è l'interfaccia "tipo" (ad esempio un protocollo multimethod o funzione.): Aggiunge un elemento alla collezione. Quindi qualcuno che fornisce un nuovo tipo di raccolta deve solo implementare il semplice caso di aggiungere un singolo argomento. E automagicamente il nuovo tipo supporterà anche l'aggiunta di più elementi.

Ma ora supponiamo di avere un tipo di raccolta, che consente un modo più rapido per aggiungere diversi elementi rispetto all'aggiunta di uno dopo l'altro. Questa funzionalità non può essere utilizzata ora.

Così si esegue la funzione multimetodo/protocollo della funzione conj. Ora la collezione può usare il modo più veloce. Ma ogni implementazione deve fornire la pluralità di elementi standard.

Questo è un trade-off e fino alla vostra decisione. Non c'è il modo giusto (tm). Entrambi possono essere considerati idiomatici. (Anche se personalmente proverei ad andare con il primo il più spesso possibile.)

YMMV.

Modifica: Un esempio di metodi multi-area senza codifica nel valore di invio.

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

mi piace questo e la risposta di Ankur, ma questo usa arity sovraccarico v s l'altro che usa il conteggio degli argomenti per abbinare il valore di dispatch. Suppongo che abbia senso utilizzare l'approccio defn se si desidera la stessa funzione predefinita per ogni valore di dispatch (ed evitare la duplicazione) rispetto a overloading a livello di defmethod se si desidera un valore predefinito diverso per il valore di dispatch. –

3

È possibile farlo usando multimethods come mostrato di seguito Esempio:

(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

Fondamentalmente si può inviare su qualsiasi cosa, né il tipo né il numero di args deve essere consistent..like questo:

(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)) 

Le possibilità sono infinite

Problemi correlati