Как бы вы идиоматически расширили арифметические функции для других типов данных в 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. Он использует мультиметоды.