Вопрос по – Можно ли перегрузить мульти-методы Clojure на arity?

17

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

Вот пример:

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

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

Ваш Ответ

3   ответа
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

По сути, вы можете отсылать что угодно, но ни тип, ни количество аргументов не должны быть согласованными ... вот так:

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

Возможности безграничны

14

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

& Quot; пользователь & quot; интерфейс в вашем случаеwhich-colour, Тип & quot; интерфейсwhich-colour-mm (хорошо, не совсем, но только ради аргумента). Пользователь вашей библиотеки не должен знать о мультиметоде.

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

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

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

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

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

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

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

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

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

YMMV.

Edit: Пример многоартериальных методов без кодирования в значении отправки.

(defmulti which-colour-mm (fn [m & args] (:colour m)))
(defmethod which-colour-mm :blue
  ([m] (print m))
  ([m f] (f m)))
Мне нравится этот ответ и ответ Анкура, но этот использует перегрузку арности по сравнению с другим, который использует количество аргументов для соответствия значению диспетчеризации. Я полагаю, что имеет смысл использовать подход defn, если вы хотите использовать одну и ту же функцию по умолчанию для каждого значения диспетчеризации (и избегать дублирования) по сравнению с перегрузкой на уровне defmethod, если вам нужно другое значение по умолчанию для каждого значения диспетчеризации. Andrew Whitehouse

Похожие вопросы