Clojure: добавление функций в defrecord без определения нового протокола

Я использую OO в python/java. Выполнение 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.