10 Stimmen

Implementierung von benutzerdefinierten Datenstrukturen mit Clojure-Protokollen

Ich kann den ganzen Punkt über Protokolle verpasst haben, aber meine Frage ist, können Protokolle verwendet werden, um zu diktieren, wie eine benutzerdefinierte Datenstruktur zu iterieren oder wie println das Objekt drucken würde?

Angenommen eine Karte mit zwei Vektoren,

{:a [] :b []}

Wenn ich sie zuerst aufrufe, möchte ich den Vektor :a nehmen, aber wenn ich diese Struktur aufrufe, möchte ich sie mit :b verbinden. Kann ich Protokolle verwenden, um diese Art von Verhalten zu erreichen?

13voto

Michał Marczyk Punkte 82196

Einige Dinge sind immer noch als Java-Schnittstellen in Clojure implementiert; von diesen werden einige wahrscheinlich für immer so bleiben, um die Zusammenarbeit mit Clojure-Code aus anderen JVM-Sprachen zu erleichtern.

Glücklicherweise kann man bei der Definition eines Typs mit deftype können Sie den neuen Typ alle Java-Schnittstellen implementieren lassen, die Sie benötigen (was Brian in einem Kommentar oben erwähnt hat), sowie alle Methoden von java.lang.Object . Ein Beispiel, das Ihrer Beschreibung entspricht, könnte wie folgt aussehen:

(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)))

Ein Beispiel dafür, was man damit in der REPL machen kann:

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)

Beachten Sie, dass die REPL druckt es als eine seq; Ich glaube, das ist wegen der Inline-Implementierung von clojure.lang.ISeq . Sie können es weglassen und die seq Methode mit einer zurückgebenden (seq a) für eine gedruckte Darstellung unter Verwendung der benutzerdefinierten toString . str verwendet immer toString Allerdings.

Wenn Sie ein benutzerdefiniertes Verhalten von pr Familienfunktionen (einschließlich println usw.), müssen Sie sich mit der Implementierung eines benutzerdefinierten print-method für Ihren Typ. print-method ist eine Multimethode, die in clojure.core ; schauen Sie sich an core_print.clj in den Clojure-Quellen für Beispielimplementierungen.

0voto

optevo Punkte 1896

Ich habe mit benutzerdefinierten Sammlungen gespielt und wollte die Ausgabe an die REPL anpassen, also habe ich Michals Rat zum letzten Punkt befolgt. Ich habe den Codeschnipsel beigefügt, weil ich fand, dass das Durchsuchen des Quellcodes eine Weile dauerte, da ich nirgendwo anders eine Erklärung dafür fand.

(defmethod print-method your.custom.collection.goes.Here [c, ^java.io.Writer w]
    (.write w (str "here is my custom output: " c)))

Dies ist zum Beispiel dann praktisch, wenn die seq immer Ihren benutzerdefinierten Vektor mit Klammern ausgibt (wie in Michals Beispiel) und Sie wollen eckige Klammern wie bei normalen Clojure-Vektoren:

(defmethod print-method your.custom.Vector [v, ^java.io.Writer w]
    (.write w (str (into [] v))))

Das bedeutet auch, dass der Weg nun seq um tatsächlich eine Sequenz Ihres Datentyps zurückzugeben, anstatt sie nur für die REPL-Ausgabe zu implementieren.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X