2
votes

I've got a piece of code to implement a ref of a map of atoms containing maps

 > (def a (example.DynaRec.)

 > (dosync (assoc! a 3 {:id 3 :contents "stuff"} 4 {:id 4}))
 ;;=> #[DynaRec@1a659078: {4 [<Atom@118eb00c: {:id 4}] 3 [Atom@242110fc: {:id 3 :contents "stuff"}]]

I want to make change the way this is displayed in the repl so that it only outputs

> a 
;;=> ({:id 3 :contents "stuff"}
;;    {:id 4})

how can this be done? The code is shown below:

(ns example.dyna-rec
  (:gen-class
   :name example.DynaRec
   :prefix "-"
   :init init
   :state state
   :extends clojure.lang.AFn
   :implements [clojure.lang.IDeref
                clojure.lang.Seqable
                clojure.lang.ILookup
                clojure.lang.ITransientMap]))

(defn- $ [this] (:data (.state this)))

(defn- valid?
  ([e]
     (valid? e []))
  ([this e]
     (every? #(contains? e %) (:required (.state this)))))

(defn -deref [this] @($ this))

(defn -valAt
  ([this k] ((-deref this) k nil))
  ([this k nv] 
    (if-let [vatom ((-deref this) k nv)]
      @vatom)))

(defn -invoke
  ([this k] (-valAt this k))
  ([this k nv] -valAt this k nv))

(defn -seq [this] (seq (-deref this)))

(defn -count [this] (count (-deref this)))

(defn -without [this obj]
  (alter ($ this) dissoc obj))

(defn -assoc
  ([this obj] (-assoc this (:id obj) obj))
  ([this k v]
   {:pre [(valid? this v)
          (= k (:id v))]} 
    (alter ($ this) assoc k (atom v))
    this))

(defn -conj [this obj]
  (-assoc this obj))

(defn -persistent [this]
  (-deref this))

(defn -init []
  [[]  {:required  #{:id}
        :data      (ref {})}])
1

1 Answers

5
votes

To extend the classic java model (which clojure embraces) you can implement your own .toString which produces the correct strings, and then tell the REPL how to print this new class properly

start by adding a super basic toString to dyna-rec.clj:

(defn- -toString [this]
  (str (into {} this)))

this can be called like this:

 (defn- -toString [this]
 (str (zipmap (keys this) (map deref (vals this)))))

example.DynaRec> (.toString a)
"{}"
example.DynaRec> (str a)
"{}"

then improve the printer to match your desired output:

(defn- -toString [this]
  (str (zipmap (keys this) (map deref (vals this)))))

and test it:

example.DynaRec> (str a)
"{3 {:id 3, :contents \"stuff\"}, 4 {:id 4}}"
example.DynaRec> 

This still does not print correctly at the REPL, so we need to extend the multimethod used by the printer (print-method) which is used by the REPL to know about your new class:

(defmethod print-method 
  example.DynaRec
  [this, w]
  (print-method (zipmap (keys this) (map deref (vals this))) w))

this produces what you seek:

example.DynaRec> a
{3 {:id 3, :contents "stuff"}, 4 {:id 4}}