Реализация пользовательских структур данных с использованием протоколов Clojure
Возможно, я пропустил весь вопрос о протоколах, но мой вопрос заключается в том, могут ли протоколы использоваться для указания способа итерации пользовательской структуры данных или как println будет печатать объект?
Предполагая карту с двумя векторами,
{:a [] :b []}
При первом вызове я хотел бы взять из вектора: a, но применительно к этой структуре я хотел бы соединиться с: b. Могу ли я использовать протоколы для достижения такого типа поведения?
2 ответа
Некоторые вещи все еще реализованы как интерфейсы Java в Clojure; Я бы сказал, что некоторые из них, вероятно, навсегда останутся такими, чтобы облегчить сотрудничество с кодом Clojure из других языков JVM.
К счастью, при определении типа с помощью deftype
вы можете иметь новый тип, реализующий любые необходимые вам интерфейсы Java (о которых Брайан упомянул в комментарии выше), а также любые методы java.lang.Object
, Пример, соответствующий вашему описанию, может выглядеть так:
(deftype Foo [a b]
clojure.lang.IPersistentCollection
(seq [self] (if (seq a) self nil))
(cons [self o] (Foo. a (conj b o)))
(empty [self] (Foo. [] []))
(equiv
[self o]
(if (instance? Foo o)
(and (= a (.a o))
(= b (.b o)))
false))
clojure.lang.ISeq
(first [self] (first a))
(next [self] (next a))
(more [self] (rest a))
Object
(toString [self] (str "Foo of a: " a ", b: " b)))
Пример того, что вы можете сделать с ним в REPL:
user> (.toString (conj (conj (Foo. [] []) 1) 2))
"Foo of a: [], b: [1 2]"
user> (.toString (conj (conj (Foo. [:a :b] [0]) 1) 2))
"Foo of a: [:a :b], b: [0 1 2]"
user> (first (conj (conj (Foo. [:a :b] [0]) 1) 2))
:a
user> (Foo. [1 2 3] [:a :b :c])
(1 2 3)
Обратите внимание, что REPL печатает его как последовательность; Я считаю, что это из-за встроенной реализации clojure.lang.ISeq
, Вы можете пропустить это и заменить seq
метод с одним возвратом (seq a)
для печатного представления с использованием обычая toString
, str
всегда использует toString
, хоть.
Если вам нужно пользовательское поведение pr
семейные функции (в том числе println
и т. д.), вам придется изучить реализацию print-method
для вашего типа. print-method
это мультиметод, определенный в clojure.core
; взгляните на core_print.clj в исходных кодах Clojure для примеров реализации.
Я играл с пользовательскими коллекциями и хотел настроить вывод для REPL, поэтому в итоге я последовал совету Михала по последнему пункту. Я включил фрагмент кода о том, как это сделать, потому что я обнаружил, что просеивание через источник заняло у меня некоторое время, так как я не нашел этого объяснения где-либо еще.
(defmethod print-method your.custom.collection.goes.Here [c, ^java.io.Writer w]
(.write w (str "here is my custom output: " c)))
Это удобно, например, в случаях, когда seq
всегда печатает ваш собственный вектор в скобках (как в примере Михала), и вам нужны квадратные скобки, такие как обычные векторы Clojure:
(defmethod print-method your.custom.Vector [v, ^java.io.Writer w]
(.write w (str (into [] v))))
Это также означает, что способ может теперь реализовать seq
на самом деле вернуть последовательность вашего типа данных, а не просто реализовать ее для вывода REPL.