Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing Custom Data Structures Using Clojure Protocols

Tags:

clojure

I may have missed the whole point about protocols but my question is, can protocols be used to dictate how to iterate a custom data structure or how println would print the object?

Assuming a map with two vectors,

{:a [] :b []}

When called first on it I would like to take from the :a vector but when conj on this structure i would like to conj to :b. Can I use protocols to achieve this type of behavior?

like image 713
Hamza Yerlikaya Avatar asked May 31 '10 14:05

Hamza Yerlikaya


People also ask

What is a Clojure protocol?

A protocol is a named set of named methods and their signatures, defined using defprotocol: (defprotocol AProtocol "A doc string for AProtocol abstraction" (bar [a b] "bar docs") (baz [a] [a b] [a b c] "baz docs")) No implementations are provided. Docs can be specified for the protocol and the functions.

What is a collection in Clojure?

Clojure collections "collect" values into compound values. There are four key Clojure collection types: vectors, lists, sets, and maps. Of those four collection types, vectors and lists are ordered.


1 Answers

Some things are still implemented as Java interfaces in Clojure; of those, I'd say some are likely to stay that way forever to ease cooperating with Clojure code from other JVM languages.

Fortunately, when defining a type using deftype, you can have the new type implement any Java interfaces you require (which Brian mentioned in a comment above), as well as any methods of java.lang.Object. An example to match your description might look like this:

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

A sample of what you can do with it at the 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)

Note that the REPL prints it as a seq; I believe that's because of the inline implementation of clojure.lang.ISeq. You could skip it and replace the seq method with one returning (seq a) for a printed representation using the custom toString. str always uses toString, though.

If you need custom behaviour of pr family functions (including println etc.), you'll have to look into implementing a custom print-method for your type. print-method is a multimethod defined in clojure.core; have a look at core_print.clj in Clojure's sources for example implementations.

like image 158
Michał Marczyk Avatar answered Sep 20 '22 12:09

Michał Marczyk