3

I have a java class in clojure, which comes from a method that returns classes. I want to case switch on these, like so:

            (case type
                java.lang.String (println "Found String" name)
                java.lang.Long (println "Found Long" name)
                java.nio.ByteBuffer (println "Found ByteBuffer" name)
                java.lang.Boolean (println "Found Boolean" name)
                java.math.BigDecimal (println "Found BigDecimal" name)
                java.lang.Double (println "Found Double" name)
                java.lang.Float (println "Found Float" name)
                java.net.InetAddress (println "Found InetAddress" name)
                java.lang.Integer (println "Found Integer" name)
                java.util.Date (println "Found Date" name)
                java.util.UUID (println "Found UUID" name)
                java.math.BigInteger (println "Found BigInteger" name)
                java.util.List (println "Found List" name)
                java.util.Set (println "Found Set" name)
                java.util.Map (println "Found Map" name))

But when I run this I get

 java.lang.IllegalArgumentException: No matching clause: class java.util.UUID

Which is what is thrown when no matching case is found. How do I match the class in the case clause?

amalloy
  • 89,153
  • 8
  • 140
  • 205
David Williams
  • 8,388
  • 23
  • 83
  • 171
  • well, nice to know this isn't supposed to work. but maybe it is hiding in the answers, what is the reason this doesn't work as expected? – matanster May 14 '17 at 03:54

4 Answers4

5

You can't really do any better than what cheshire does, which is basically repeated calls to instance?, like this:

(condp instance? x
  String :string
  Integer :int
  :unknown)

If you don't want to pay attention to subtyping, and use only an exact match on x's type, you can use (condp = (class x) ...) instead.

amalloy
  • 89,153
  • 8
  • 140
  • 205
3

Use a map instead of a case form:

def case-map
  {java.util.Set "Set",
   java.math.BigInteger "BigInteger",
   java.lang.Double "Double",
   java.math.BigDecimal "BigDecimal",
   java.util.List "List",
   java.lang.Float "Float",
   java.util.UUID "UUID",
   java.lang.String "String",
   java.lang.Integer "Integer",
   java.nio.ByteBuffer "ByteBuffer",
   java.lang.Boolean "Boolean",
   java.net.InetAddress "InetAddress",
   java.util.Date "Date",
   java.util.Map "Map",
   java.lang.Long "Long"})

(defn what-is [x] (str (case-map (type x)) " " x))

For instances:

(what-is (java.util.Date.))
"Date Mon Sep 22 08:17:55 BST 2014"

(what-is (java.util.UUID. 0 0))
"UUID 00000000-0000-0000-0000-000000000000"

Edit: The warning in @cgrand's answer against AOT compilation appears to apply to this solution too.

Community
  • 1
  • 1
Thumbnail
  • 13,293
  • 2
  • 29
  • 37
  • You can avoid the AOT compilation issue if you build the map inside the function instead of outside. Of course, you're then paying the cost of building it over and over. – amalloy Sep 23 '14 at 04:20
  • 1
    Wow, did I really suggest building this map over and over? A much better way to address AOT is to define a delay of the map, rather than the map itself, so that it gets built the first time you use it instead of at compile time or over and over again: `(def case-map (delay {...})) (defn what-is [x] (get @case-map (class x)))` – amalloy Dec 08 '14 at 20:30
1

Here is how to cheat around this issue:

=> (map #(case (class %)
           #=java.lang.String (str "Found String " %)
           #=java.lang.Long (str "Found Long " %))
     ["a" 42])
("Found String a" "Found Long 42")

However since classes don't have a stable hashcode, never use that on AOT-compiled code.

cgrand
  • 7,939
  • 28
  • 32
  • [This post](http://stackoverflow.com/questions/6427967/clojure-reader-macro#6431561) questions the status of `#= ...` as of two or three years ago. And it's still not documented. Is it safe to use it? – Thumbnail Sep 22 '14 at 14:32
  • @Thumbnail No. Don't use it. – amalloy Sep 22 '14 at 23:56
0

I ended up using multimethods

(defmulti get-row-data-for-class (fn [type-class name row] type-class))
(defmethod get-row-data-for-class java.lang.Boolean [type-class name row] (.getBool row name))
(defmethod get-row-data-for-class java.lang.Double [type-class name row] (.getDouble row name))
(defmethod get-row-data-for-class java.lang.Float [type-class name row] (.getFloat row name))

Then something like

(let [data (get-row-data-for-class type-class name row)])
David Williams
  • 8,388
  • 23
  • 83
  • 171