Cljs case & ^:const

so… i have this silly problem involving case and ^:const in cljs… and in my case i am pretty sure it would be best to simply go with condp = … which works perfectly fine for me… and for me this is clearly no performance bottleneck … so… this morning i wanted to ask about this… then i thought better of it, because premature optimization is the root of all evil… but now i am thinking… well… why not ask if someone has already dealt with the same issue… and has found a simple / clean solution for it… which i could borrow… because even if it doesn’t matter in terms of performance… i still would find it nicer to go with ‘case’ instead of ‘condp =’…

…so … here is the thing…

i use luminus to generate my project template…

lein new luminus case_condp +http-kit +postgres +reagent +auth

i fire up emacs… cider-jack-in-clj&cljs… figwheel…

alright… now… if i use something like:

(def ^:const FOO :FOO)

with something like…

(condp = x
  FOO "match for x = :FOO"
  "default")

there is no problem… even… while i am developing… but when i change that to:

(case x
  FOO "works in prod... but not in dev... for x = :FOO"
  "default")

this works just fine after i push to production… but in dev… this stops to work for me… and so i end up with the default clause…

any ideas?

Your text is very hard to read with “…” scattered throughout it. I think what you’re running into is the fact that case does not resolve symbols used in the match on the left hand side. So:

(def FOO "bar")

(case FOO
  FOO "baz"
  "default")
;; => default

Like you said, since it will try and match the value “baz” to the symbol FOO.

(def XYZ 'FOO)

(case XYZ
  FOO "baz"
  "default")
;; => "baz"

So for your case, you should continue to use condp = if you want to store the value you want to match against inside of a var.

1 Like

thx for answering so quickly!.. also… reading the dots is just like reading s-exp… it just takes some time to get used to… but then it really is great fun :smile:

This is a trickier situation then what @lilactown described. The ^:const will mean that on compilation of the code, all instances of the symbol FOO will be replaced by the value of the Var. This explains why it works for you in production. I’m guessing there’s something in your dev environment which bypasses that step from the compiler. I don’t know enough of the details though to make a guess.

2 Likes

The Clojure API documentation (https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/case) says, “The test-constants are not evaluated. They must be compile-time literals, and need not be quoted.”

The API doc of ClojureScript says the same (http://cljs.github.io/api/cljs.core/#case), and might be true in some sense, but in fact constants might get substituted. The difference is noted on “Differences from Clojure” (https://clojurescript.org/about/differences#_special_forms).

Mike Fikes wrote in 2015 that the assortment of tests in a case affects the compiler’s decision whether to emit a JavaScript switch or a series of ifs, which do different things with declared consts (https://blog.fikesfarm.com/posts/2015-06-15-clojurescript-case-constants.html). Controversy erupted in 2017 on the Google Group: https://groups.google.com/d/msg/clojure/u1RZsmjbQ64/p7B9eRwuAQAJ

Bottom line, case-using-symbols is not portable between Clojure and ClojureScript. In ClojureScript, its meaning has been unstable by Clojure standards, and in my opinion it is currently a bit brittle. Avoid.

2 Likes

https://blog.fikesfarm.com/posts/2017-06-28-clojurescript-const-var-inlining.html

Interesting. I think the issue is Clojure constant are inlined after macro-expansion it seems? Where as ClojureScript is now inlining them more at read time ?

…hmm… i think one issue is this compiler option:
https://clojurescript.org/reference/compiler-options#optimize-constants