Реализация пользовательских структур данных с использованием протоколов 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.

Другие вопросы по тегам