clojure

Make vars constant for use in case statements in Clojure


In Clojure, is there a way to make a var constant such that it can be used in case statements?

e.g.

(def a 1)
(def b 2)

(let [x 1]
  (case x
    a :1
    b :2
    :none))
=> :none

I understand I can use something like cond or condp to get around this, but it would be nice if I could define something that does not require further evaluation so I could use case.


Solution

  • Related and answer stolen from it:

    As the docstring tells you: No you cannot do this. You can use Chas Emericks macro and do this however:

    (defmacro case+
      "Same as case, but evaluates dispatch values, needed for referring to
       class and def'ed constants as well as java.util.Enum instances."
      [value & clauses]
      (let [clauses (partition 2 2 nil clauses)
            default (when (-> clauses last count (== 1))
                      (last clauses))
            clauses (if default (drop-last clauses) clauses)
            eval-dispatch (fn [d]
                            (if (list? d)
                              (map eval d)
                              (eval d)))]
        `(case ~value
           ~@(concat (->> clauses
                       (map #(-> % first eval-dispatch (list (second %))))
                       (mapcat identity))
               default))))
    

    Thus:

    (def ^:const a 1)
    (def ^:const b 2)
    (let [x 1]
      (case+ x
        a :1
        b :2
        :none))
    => :1
    

    An alternative (which is nice since it's more powerful) is to use core.match's functionality. Though you can only match against local bindings:

    (let [x 2
          a a
          b b]
      (match x
        a :1
        b :2
        :none))
     => :2