2012-04-25 4 views
17

У меня есть код, который использует несколько методов и в идеале хотел бы перегрузить функцию (в данном случае, многофункциональную), чтобы я мог, например, передать функцию более высокого порядка, чтобы помочь в тестировании.Можно ли перегрузить Clojure мульти-методы на arity?

Вот пример:

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

Так что мой defn обеспечивает Арность перегружать, но мне интересно, если DEFMETHOD поддерживает что-нибудь подобное. (Думаю, вы не захотели бы это сделать для каждой декларации defmethod.)

Это самый подходящий (смею я говорю, идиоматический) подход, или есть лучший способ?

ответ

14

Это прекрасно. Существует интерфейс «пользователь» и «тип» интерфейса библиотеки. Они могут быть одинаковыми, но они не обязательно.

«Пользовательский» интерфейс находится в вашем случае which-colour. Интерфейс «type» равен which-colour-mm (хорошо, не совсем, но только ради аргумента). Пользователь вашей библиотеки не должен знать о мультиметоде.

С другой стороны, кому-то, предлагающему новый цвет, скажем :purple, не нужно заботиться о многогранном шаблоне. Это обрабатывается для него в which-colour.

Это совершенно правильный дизайн!

Но, конечно, есть ценник. Предположим, у вас есть цвет, у которого есть еще более перманентный способ сделать что-то ... Теперь вы заперты в возможно более медленный интерфейс.

Чтобы это было немного разъяснено: предположим, что у вас есть интерфейс коллекции. Вы предоставляете функцию - conj - которая позволяет пользователю добавлять элементы в коллекцию. Он реализуется следующим образом:

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

conj1 является «тип» интерфейс (например, мультиметод или протокол функции.): Он добавляет один элемент в коллекции. Поэтому кто-то, поставляющий новый тип коллекции, должен реализовать простой случай добавления одного аргумента. Автоматически новый тип также будет поддерживать добавление нескольких элементов.

Теперь предположим, что у вас есть тип коллекции, который позволяет быстрее добавлять несколько элементов, чем просто добавлять друг за другом. Эта возможность не может быть использована сейчас.

Таким образом, вы делаете функцию multimethod/protocol самой функцией conj. Теперь коллекция может использовать более быстрый способ. Но каждая реализация должна обеспечивать набор шаблонов нескольких элементов.

Это компромисс и ваше решение. Существует не правильный путь (tm). Оба могут считаться идиоматическими. (Хотя я лично постараюсь пойти с первым как можно чаще.)

YMMV.

Редактировать: Пример методов многогранности без кодирования в значении отправки.

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

Мне это нравится, и ответ ANKUR, но это один использует Arity перегружать vs другой, который использует счетчик аргументов, чтобы соответствовать значению отправки. Я полагаю, что имеет смысл использовать подход defn, если вы хотите использовать одну и ту же функцию по умолчанию для каждого значения отправки (и избегать дублирования) и перегрузки на уровне defmethod, если вам нужно другое значение по умолчанию для каждого значения отправки. –

3

Вы можете сделать это с помощью мультиметоды, как показано ниже, например:

(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

В принципе вы можете разослать на что-либо, ни тип, ни количество аргументов должно быть consistent..like это:

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

возможности бесконечны