Можно ли перегрузить 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 обеспечивает перегрузку arity, но мне интересно, поддерживает ли defmethod что-то вроде этого. (Я думаю, вы не захотите делать это для каждой декларации defmethod.)

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

Ответы

Ответ 1

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

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

С другой стороны, кому-то, предлагающему новый цвет - скажем :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)))

Ответ 2

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

(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

Ответ 3

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

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

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