Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to use Clojure's case form with a Java enum?

The case doc says

Unlike cond and condp, case does a constant-time dispatch... All manner of constant expressions are acceptable in case.

I would like to benefit from case's constant-time dispatch to match on Java enums. Java's switch statement works well with enums, but doing the following in Clojure:

(defn foo [x] 
   (case x 
      java.util.concurrent.TimeUnit/MILLISECONDS "yes!"))

(foo java.util.concurrent.TimeUnit/MILLISECONDS)

Results in: IllegalArgumentException No matching clause: MILLISECONDS

Are enums not supported in case? Am I doing something wrong? Must I resort to cond or is there a better solution?

like image 809
pron Avatar asked May 27 '13 17:05

pron


2 Answers

The problem here is that case's test constants, as described in the docs, " must be compile-time literals". So, rather than resolving java.util.concurrent.TimeUnit/MILLISECONDS, the literal symbol 'java.util.concurrent.TimeUnit/MILLISECONDS is being tested against.

(foo java.util.concurrent.TimeUnit/MILLISECONDS) ; IllegalArgumentException
(foo 'java.util.concurrent.TimeUnit/MILLISECONDS) ; yes!

Instead, the solution is to dispatch on the .ordinal of the Enum instance, which is what Java itself does when compiling switch statements over enums:

(defn foo [x]
  (case (.ordinal x)
    2 "yes!"))

You can wrap this pattern in a macro which correctly evaluates the case ordinals for you:

(defmacro case-enum
  "Like `case`, but explicitly dispatch on Java enum ordinals."
  [e & clauses]
  (letfn [(enum-ordinal [e] `(let [^Enum e# ~e] (.ordinal e#)))]
    `(case ~(enum-ordinal e)
       ~@(concat
          (mapcat (fn [[test result]]
                    [(eval (enum-ordinal test)) result])
                  (partition 2 clauses))
          (when (odd? (count clauses))
            (list (last clauses)))))))
like image 166
Beyamor Avatar answered Sep 22 '22 22:09

Beyamor


You could use use a cond on the name of the enumm

(case (.name myEnumValue) "NAME_MY_ENUM" (println "Hey, it works!"))

Seems to me very simple compared to the alternatives

like image 45
Federico Tomassetti Avatar answered Sep 24 '22 22:09

Federico Tomassetti