Как бы вы идиоматически расширили арифметические функции для других типов данных в Clojure?

Итак, я хочу использовать java.awt.Color для чего-то, и я бы хотел написать такой код:

(use 'java.awt.Color)
(= Color/BLUE (- Color/WHITE Color/RED Color/GREEN))

Рассматривая основную реализацию -, речь идет конкретно о clojure.lang.Numbers, что для меня подразумевает, что я ничего не делаю, чтобы "подключиться" к основной реализации и расширить ее.

Оглядываясь в Интернете, кажется, есть две разные вещи:

  • Напишите свою собственную функцию defn -, которая знает только о том типе данных, который им интересен. Чтобы использовать, вероятно, вы закончите префикс пространства имен, что-то вроде:

    (= Color/BLUE (scdf.color/- Color/WHITE Color/RED Color/GREEN))

    Или, альтернативно, use в пространстве имен и используйте clojure.core/-, если вы хотите математику чисел.

  • Составьте специальный случай в реализации -, который проходит до clojure.core/-, когда ваша реализация передается Number.

К сожалению, мне не нравится ни одно из них. Первое, пожалуй, самое чистое, поскольку второе делает предположение, что единственное, что вам нравится в математике, это их новый тип данных и номера.

Я новичок в Clojure, но не должен ли мы использовать Протоколы или Мультиметоды здесь, так что, когда люди создают/используют пользовательские типы, они могут "расширять" эти функции, чтобы они работали без видимых усилий? Есть ли причина, по которой +, - и т.д. Не поддерживает это? (или они? Они, кажется, не от моего чтения кода, но, возможно, я читаю его неправильно).

Если я хочу написать свои собственные расширения для обычных существующих функций, таких как + для других типов данных, как мне это сделать, чтобы он отлично сочетался с существующими функциями и потенциально другими типами данных?

Ответ 1

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

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

Другая проблема с этими функциями будет их вариационным характером (т.е. они могут принимать любое количество аргументов). Это серьезная проблема в обеспечении реализации протокола, поскольку проверка расширенного типа протокола работает только с первым параметром.

Ответ 2

Он не был специально разработан для этого, но core.matrix может вас заинтересовать здесь по нескольким причинам:

  • Исходный код содержит примеры использования протоколов для определения операций, которые работают с различными типами. Например, (+ [1 2] [3 4]) => [4 6]). Стоит изучить, как это делается: в основном операторы являются регулярными функциями, которые вызывают протокол, и каждый тип данных обеспечивает реализацию протокола через extend-protocol
  • Вам может быть интересно сделать работу java.awt.Color как реализацию core.matrix(т.е. как 4D RGBA-вектор). Я сделал что-то похожее на BufferedImage здесь: https://github.com/clojure-numerics/image-matrix. Если вы реализуете базовые протоколы core.matrix, вы получите весь API core.matrix для работы с объектами Color. Это сэкономит вам много работы, выполняя различные операции.

Ответ 3

Вы можете посмотреть algo.generic.arithmetic в algo.generic. Он использует мультиметоды.