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

若如初见. 提交于 2019-12-06 20:05:59

问题


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?


回答1:


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)))))))



回答2:


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




回答3:


Here's a simpler solution that just uses equality checking on the cases -

(defn cases [v & args]
  (let [clauses (partition 2 2 args)]
    (some #(when (= (first %) v) (second %)) clauses))) 

=> (cases EventType/received EventType/send "A" EventType/received "B")
=> "B"


来源:https://stackoverflow.com/questions/16777814/is-it-possible-to-use-clojures-case-form-with-a-java-enum

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!