Search code examples
clojureinterface

trouble displaying type that implements IPersistentMap interface


I am implementing a type that is really just a wrapper for hash-maps

(defn as-pairs [m]
  (when-not (empty? m)
    (seq (persistent! (reduce (fn [s [k vs]]
                                (reduce (fn [s v] (conj! s [k v])) s vs)) (transient []) m)))))


(deftype Rel [m]
  clojure.lang.Seqable
  (seq [this] (as-pairs m))
  clojure.lang.ILookup
  (valAt [this k] (get m k))
  (valAt [this k default] (get m k default))
  clojure.lang.IPersistentMap
  (assoc [this k v] (Rel. (update m k #(conj (or % #{}) v))))
  (assocEx [this k v] (throw (Exception.)))
  (without [this k] (Rel. (dissoc m k))))

(defn relation [] (Rel. (hash-map)))

It seems to be working as expected

state-machines.maps> (def r (relation))
#'state-machines.maps/r
state-machines.maps> (type r)
state_machines.maps.Rel
state-machines.maps> r
{} ; interesting that it actually displays a map! -- source of problem?
state-machines.maps> (type (assoc r :foo 1 :foo 2 :bar 1))
state_machines.maps.Rel
state-machines.maps> (get (assoc r :foo 1 :foo 2 :bar 1) :foo)
#{1 2}
state-machines.maps> (seq (assoc r :foo 1 :foo 2 :bar 1))
([:foo 1] [:foo 2] [:bar 1])
state-machines.maps> (assoc r :foo 1 :foo 2 :bar 1)
ClassCastException clojure.lang.PersistentVector cannot be cast to java.util.Map$Entry  clojure.core/key (core.clj:1518)
state-machines.maps> 

looking at the stack trace

1. Unhandled java.lang.ClassCastException
   clojure.lang.PersistentVector cannot be cast to java.util.Map$Entry

                  core.clj: 1518  clojure.core/key
            core_print.clj:  212  clojure.core/print-map/fn
            core_print.clj:   59  clojure.core/print-sequential
            core_print.clj:  208  clojure.core/print-map
            core_print.clj:  217  clojure.core/fn
            core_print.clj:  217  clojure.core/fn
              MultiFn.java:  233  clojure.lang.MultiFn/invoke
             pr_values.clj:   35  clojure.tools.nrepl.middleware.pr-values/pr-values/fn/reify
    interruptible_eval.clj:  113  clojure.tools.nrepl.middleware.interruptible-eval/evaluate/fn/fn
                  main.clj:  241  clojure.main/repl/read-eval-print
                  main.clj:  258  clojure.main/repl/fn
                  main.clj:  258  clojure.main/repl
                  main.clj:  174  clojure.main/repl
               RestFn.java:  137  clojure.lang.RestFn/applyTo
                  core.clj:  646  clojure.core/apply
                  core.clj:  641  clojure.core/apply
                regrow.clj:   18  refactor-nrepl.ns.slam.hound.regrow/wrap-clojure-repl/fn
               RestFn.java: 1523  clojure.lang.RestFn/invoke
    interruptible_eval.clj:   87  clojure.tools.nrepl.middleware.interruptible-eval/evaluate/fn
                  AFn.java:  152  clojure.lang.AFn/applyToHelper
                  AFn.java:  144  clojure.lang.AFn/applyTo
                  core.clj:  646  clojure.core/apply
                  core.clj: 1881  clojure.core/with-bindings*
                  core.clj: 1881  clojure.core/with-bindings*
               RestFn.java:  425  clojure.lang.RestFn/invoke
    interruptible_eval.clj:   85  clojure.tools.nrepl.middleware.interruptible-eval/evaluate
    interruptible_eval.clj:   55  clojure.tools.nrepl.middleware.interruptible-eval/evaluate
    interruptible_eval.clj:  222  clojure.tools.nrepl.middleware.interruptible-eval/interruptible-eval/fn/fn
    interruptible_eval.clj:  190  clojure.tools.nrepl.middleware.interruptible-eval/run-next/fn
                  AFn.java:   22  clojure.lang.AFn/run
   ThreadPoolExecutor.java: 1142  java.util.concurrent.ThreadPoolExecutor/runWorker
   ThreadPoolExecutor.java:  617  java.util.concurrent.ThreadPoolExecutor$Worker/run
               Thread.java:  745  java.lang.Thread/run

I am assuming that something is happening in one of

    core_print.clj:  212  clojure.core/print-map/fn
    core_print.clj:   59  clojure.core/print-sequential
    core_print.clj:  208  clojure.core/print-map

Is this an interface that I need to implement?

After updating as-pairs to

(defn as-pairs [m]
  (when-not (empty? m)
    (seq (persistent! (reduce (fn [s [k vs]]
                                (reduce (fn [s v] (conj! s (clojure.lang.MapEntry/create k v))) s vs)) (transient []) m)))))

the result is:

state-machines.maps> (assoc (relation) :foo 1 :foo 2 :bar 1)
{:foo 1, :foo 2, :bar 1}

Solution

  • As you suspect, the error is arising in print-map, which calls seq on its argument and then calls key and val on each element of that sequence. As the error message states, these two functions expect their argument to conform to the java.util.Map$Entry interface, and Clojure vectors do not conform to that interface.

    The solution is to take the [k v] in your as-pairs function and replace it with an expression that creates a map entry; see this question for details.