2017-02-13 17 views
1

Как можно написать defprotocoldefrecord для его реализации), объявляющий метод с тем же именем, как и существующей функции, и отправка динамически метод протокола/пластинки iff Я вызываю его с экземпляром протокола/записи, но в противном случае отправляю существующую функцию?Создание defprotocol играть хорошо Clojure (в полиморфно) с существующими функциями

Например, я хочу создать помощник геометрии, которая поддерживает основные арифметические операции (только умножение в этом примере, чтобы держать его в коротком):

(defprotocol SizeOps 
    (* [this factor] "Multiply each dimension by factor and return a new Size")) 

На данный момент я уже получаю некоторые предчувствия теряемого от компилятор:

Внимание: Протокол № 'пользователь/SizeOps является функцией перезаписи *
ВНИМАНИЕ: * уже относится к: #' clojure.core/* в пространстве имен: пользователь, заменяется на: # 'пользователь/*

Тогда реализация:

(defrecord Size [width height] 
    SizeOps 
    (* [this factor] (Size. (* width factor) (* height factor)))) 

Это компилируется нормально, но когда я пытаюсь использовать его, только * он знает, это один в моем протоколе:

(* (Size. 1 2) 10) 

IllegalArgumentException Нет реализации метода:: * протокола: # 'user/SizeOps найдено для класса: java.lang.Long

я могу взломать вокруг этого, полностью определяя функцию ядра * в моей реализации:

(defrecord Size [width height] 
    SizeOps 
    (* [this factor] (Size. (clojure.core/* width factor) (clojure.core/* height factor)))) 
(* (Size. 1 2) 10) 

# user.Size {: ширина 10,: высота 20}

Но я получаю тот же IllegalArgumentException если попытаюсь позвонить (* 3 4) позже. Я могу использовать имена clojure.core/* в моей версии defrecord, но я хочу, чтобы мои пользователи имели возможность звонить * на мои Size записи, а также на Long, Double и т. Д., Как обычно.

Похожие Q & A:

  • 5438379: расширение String с * оператором, который работает как Python: (* "!" 3)"!!!", но затеняет сердечника * так же, как в моем примере
  • 6492458: за исключением основных функций, таких как (ns user (:refer-clojure :exclude [*])) избегает предупреждения о перезаписи, но также позволяет избежать этой функции :(
  • 1535235: то же самое, с жест в сторону использования Многометодная, но без деталей

Я подозреваю, что правильное решение лежит где-то в функциональности отправки более низкого уровня, как defmulti и defmethod или deftype/derive, но я не очень знаком с нюансами Clojure's runtime polymorphism. И я буду иметь целый ряд Size, Point, Rectangle, Circle и т.д., типов, каждый из которых поддерживает некоторое подмножество +, -, *, / операции, так что я хотел бы знать, если есть способ сказать defprotocol, чтобы участвовать/строить на полиморфизме любых существующих функций, а не просто перезаписывать их.

ответ

3

В подобных случаях, когда вы сталкиваетесь с ограничениями протоколов сами по себе, это может помочь создать отдельную функцию, которая просто вызывает метод протокола для некоторых своих функций, а остальная часть того, что должно быть сделано с использованием дополнительных возможностей, которые даются регулярные defn с:

(ns example.size 
    (:refer-clojure :exclude [*]) 
    (:require [clojure.core :as clj])) 

(defprotocol SizeOps 
    (times [this factor])) 

(extend-protocol SizeOps 
    Object 
    (times [this factor] (clj/* this factor))) 

(defrecord Size [width height] 
    SizeOps 
    (times [this factor] (->Size (clj/* width factor) (clj/* height factor)))) 

(defn * 
    ([] (clj/*)) 
    ([x] (clj/* x)) 
    ([x y] (times x y)) 
    ([x y & more] (apply clj/* x y more))) 

Есть несколько преимуществ для конкретного подхода я взял здесь:

  • Все пути, кроме пути два-аргумента просто использовать arity dispatch (что быстро), и путь с двумя аргументами дополнительно дополнительно использует диспетчер протоколов (который, я думаю, так же быстро, как вы, как правило, собираетесь получить за то, что вы пытаетесь сделать)
  • Вы сохраняете все атрибуты, поэтому поведение должно быть одинаковым до clojure.core/* для обычных старых номеров

Не стесняйтесь при необходимости оптимизировать любое из них.

Наконец, чтобы продемонстрировать:

(ns example.core 
    (:refer-clojure :exclude [*]) 
    (:require [example.size :refer [* ->Size]])) 

(* (->Size 1 2) 10) ;=> #example.size.Size{:width 10, :height 20} 
(* 3 4) ;=> 12 

Надеюсь достаточно эргономичны, как упомянуто ранее.