I want to switch on the class of a given object in order to encode it.
(defn encoded-msg-for [msg]
(case (class msg)
java.lang.Double (encode-double msg)
java.lang.String (encode-str msg)
java.lang.Long (encode-int msg)
java.lang.Boolean (encode-bool msg)
clojure.lang.PersistentArrayMap (encode-hash msg)
clojure.lang.PersistentVector (encode-vec msg)
nil "~"
)
)
When I call (encoded-msg-for {})
, it returns No matching clause: class clojure.lang.PersistentArrayMap
What is odd is that putting the cases into a hash-map (with the classes as keys and strings as values) works perfectly well.
Also, (= (class {}) clojure.lang.PersistentArrayMap)
is true. What comparison is happening here and how can I switch either on the class of the object itself or (better) something in its hierarchy?
If you are dispatching only on the class then protocols might be a nice solution, because they will enable you (or your API's client) to provide implementations for other types at a later time, here is an example:
(defprotocol Encodable
(encode [this]))
(extend-protocol Encodable
java.lang.String
(encode [this] (println "encoding string"))
clojure.lang.PersistentVector
(encode [this] (println "encoding vector")))
If you need to have finer-grained dispatch or you know extending to other types is not necessary then there might be too much boilerplate in this solution.
I believe case
treats the class names as literal symbols - it does not resolve them to actual classes:
>>> (case 'clojure.lang.PersistentArrayMap clojure.lang.PersistentArrayMap 1 17)
1
>>> (case clojure.lang.PersistentArrayMap clojure.lang.PersistentArrayMap 1 17)
17
This is rather unintuitive, but so it works in Clojure's case
. Anyway, the idiomatic way is to use defmulti
and defmethod
instead of switching on type
:
(defmulti encoded-msg class)
(defmethod encoded-msg java.util.Map [x] 5)
(defmethod encoded-msg java.lang.Double [x] 7)
>>> (encoded-msg {})
5
>>> (encoded-msg 2.0)
7
The dispatcher uses the isa?
predicate which deals well with the comparisons of types, in particular it works well with Java inheritance.
If you don't want to use defmulti
, then condp
might replace case
in your use case, as it properly evaluates the test-expressions. On the other hand it doesn't provide constant time dispatch.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With