Я использую OO в python/java. Выполнение Clojure сейчас. Я столкнулся с defrecord, но мне кажется, что я должен определить протокол для каждой функции или набор функций, которые я хочу, чтобы запись выполнялась. Создание нового протокола создает трение. Я должен назвать не только функцию, которую я хочу, но и протокол. То, что я ищу, - это способ "красиво" связать функцию с записью, чтобы функция имела доступ к параметрам записи через этот параметр без необходимости определять новый протокол или добавлять функцию к существующему протоколу.
Clojure: добавление функций в defrecord без определения нового протокола
Ответ 1
Отличный вопрос.
Как обычно, есть прекрасный способ сделать что-то в Clojure - здесь, как реализовать свою собственную простую динамическую систему OO (включая наследование, полиморфизм и инкапсуляцию) в 10 строк Clojure.
Идея: вы можете поместить функции внутри нормалей Clojure карты или записи, если хотите, создавая структуру, подобную OO. Затем вы можете использовать его в стиле "prototype".
; define a prototype instance to serve as your "class"
; use this to define your methods, plus any default values
(def person-class
{:get-full-name
(fn [this] (str (:first-name this) " " (:last-name this)))})
; define an instance by merging member variables into the class
(def john
(merge person-class
{:first-name "John" :last-name "Smith"}))
; macro for calling a method - don't really need it but makes code cleaner
(defmacro call [this method & xs]
`(let [this# ~this] ((~method this#) this# [email protected])))
; call the "method"
(call john :get-full-name)
=> "John Smith"
; added bonus - inheritance for free!
(def mary (merge john {:first-name "Mary"}))
(call mary :get-full-name)
=> "Mary Smith"
Ответ 2
Если вы еще не пробовали multimethods, они могут быть ближе к тому, что вы ищете.
Определение:
(defrecord Person [first middle last])
(defmulti get-name class)
(defmethod get-name Person [person] (:first person))
Использование:
(def captain (Person. "James" "T" "Kirk"))
(get-name captain)
Выбранная реализация мультиметода основана на функции отправки в defmulti (функция, которая принимает аргументы, переданные функции, и возвращает значение отправки). Обычно "класс" - это функция отправки, как здесь, для отправки по типу. Мультиметоды поддерживают множественные независимые ad-hoc или иерархии типов на Java, реализации по умолчанию и т.д.
В целом, хотя, я думаю, возможно, вы захотите сделать шаг назад и подумать, действительно ли вам нужны протоколы или мультиметоды. Кажется, вы пытаетесь "сделать OO" в Clojure. Хотя аспекты OO (например, полиморфизма) велики, возможно, вам стоит попробовать альтернативные способы решения вашей проблемы. Например, в примере, который я только что дал, нет никакой убедительной причины (чтобы) реализовать полиморфизм get-name. Почему бы просто не сказать:
(defn get-name [x] (:first x))
Вам вообще нужна запись Person? Достаточно ли простая карта? Иногда ответы да, иногда нет.
Обычно Clojure не предоставляет наследование класса. Конечно, вы можете создать эквивалент (даже с протоколами), если вы действительно этого хотите, но, как правило, я нахожу, что есть другие более эффективные способы решения этой проблемы в Clojure.