Реализация пользовательских структур данных с использованием протоколов Clojure - PullRequest
10 голосов
/ 31 мая 2010

Возможно, я пропустил весь смысл протоколов, но мой вопрос в том, могут ли протоколы использоваться для определения способа итерации пользовательской структуры данных или как println будет печатать объект?

Предполагая карту с двумя векторами,

{:a [] :b []}

Когда он вызывается первым, я хотел бы взять из вектора:, но когда он связан с этой структурой, я хотел бы соединиться с: b. Могу ли я использовать протоколы для достижения такого типа поведения?

Ответы [ 2 ]

13 голосов
/ 31 мая 2010

Некоторые вещи все еще реализованы как интерфейсы 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 печатает его как seq; Я считаю, что это из-за встроенной реализации clojure.lang.ISeq. Вы можете пропустить его и заменить метод seq на метод, возвращающий (seq a) для печатного представления, используя пользовательский toString. str всегда использует toString, хотя.

Если вам нужно настраиваемое поведение функций семейства pr (включая println и т. Д.), Вам придется изучить реализацию настраиваемого print-method для вашего типа. print-method - это мультиметод, определенный в clojure.core; взгляните на core_print.clj в источниках Clojure для примеров реализации.

0 голосов
/ 11 мая 2014

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...