Fork me on GitHub

why would adding datomic client library to project dependencies cause figwheel to freakout with java.lang.NoSuchMethodError: 'void


Probably pulling in an older version of some shared library


Hi all - I’m coming back to hands-on ClojureScript development after some time away, and find that things have got … complicated in my absence (Leiningen vs CLI tools, Figwheel vs shadow-cljs, lein-figwheel vs Figwheel Main, …) I’m specifically just starting out on a project to build a desktop app on top of Electron. Does anyone have advice on which would be the best starting point (or any pitfalls to avoid)?


@paulbutcher i'm not sure what the situation for electron is, there might be application templates to get you up and running quickly. In general though you can think of it like this - use lein and lein figwheel. The traditionalist approach. This still just works and is very batteries included, well documented etc. - clojure cli+fw main. This is the composable approach. There's a lot less magic and stuff hidden from view. You can see the wiring so to say. This is what I would do nowadays. The trend is for simpler more focused tools that you can combine yourself. - shadow-cljs. The big selling point as far as I understand it is that it makes consuming npm libs trivial. People who use generally love it. The downside is that it's kind of its own world. People who don't use it will find it hard to help you troubleshoot HTH!


@plexus how is shadow-cljs living in its "own world"?


Thanks @plexus - that matches my impression to date. I’ve had a hunt for Electron templates and there are a few, but they all seem somewhat out of date. I’d definitely welcome pointers to anything more recent if I’ve missed anything.


@thheller FWIW, my impression of shadow-cljs was that it was probably great for someone who already understands the Node ecosystem, but for someone like me who has never used it, the documentation seemed to be assuming a lot of tacit knowledge I just don’t have (and don’t particularly want to acquire unless there’s a good reason).


@paulbutcher you don't really need to know much besides how to install node and how to use npm install react. if you are building an electron app your are going to need to get familiar with it regardless


as all the electron tooling uses that ecosystem


@thheller what I mean by that is that it lives outside of lein/clojure cli/boot. e.g. when using shadow-cljs you put your dependencies in shadow-cljs.edn instead of deps.edn for example.


yes and no. you can use deps.edn or project.clj if you like. you don't even have to use the shadow-cljs command itself if you don't want. you can run clj -m shadow.cljs.devtools.cli watch app (or via an alias, same as figwheel-main)


yes, I know that


just trying to understand why you'd describe this as a downside. just curious. I can understand people that prefer to build their own tooling instead of using something pre-made


It makes it harder to build upon my existing frame of reference


this is not a criticism, it's just a difference in taste and design choices. I like to see the wiring, because sooner or later I need to troubleshoot something and I can't do that if I don't know how things fit together. I know I can find out how things fit together, but some tools make me work harder to do that and that is generally not to my taste.


So I’m working through the instructions at and I suspect I’m missing something really simple. I can run my project and see the live tests in the browser (all good). But when I try to run the tests from the command line with “clj -A:fig:test”, I get:

Error in command line args
-- Spec failed --------------------

  ["-co" "test.cljs.edn" "-m" "hello-world.test-runner/hello-world.test-runner"]

should be a CLJS namespace available on the classpath

__ Doc for -m --main _____

  Call the -main function from a namespace with args

Detected 1 error
I suspect I’m missing something really basic - any pointers?


> should be a CLJS namespace available on the classpath


a namespace does not have a / in it, maybe try hello-world.test-runner


I’m not sure where the / is coming from - the relevant line in deps.edn looks like this:

:test  {:main-opts ["-m" "figwheel.main" "-co" "test.cljs.edn" "-m" opentrack.test-runner]}


(this is a completely un-edited project generated by the Figwheel main template)


(I’ve not changed anything at all)


(I’m going to ask this question in the figwheel-main channel)


@paulbutcher :main-opts should be only strings. no symbol. ["-m" "figwheel.main" "-co" "test.cljs.edn" "-m" "opentrack.test-runner"]


Ah! That seems to have fixed it, thanks. Looks like a bug in the Figwheel main template - I’ll put together a pull request to fix it.


Can you share your experience about server side rendering for cljs? Especially for re-frame.

Jan K19:10:16

Can someone help me understand how ClojureScript checks collection equality? I thought it was supposed to be consistent with the "hash" function, but that behaves strangely with decimal numbers, it's just truncating them:

cljs.user=> (= [1.2] [1.9])
cljs.user=> (= (hash [1.2]) (hash [1.9]))
cljs.user=> (hash 1.2)
cljs.user=> (hash 1.9)


It is probably best to consider the hash function an implementation detail and never access it manually

Jan K19:10:03

I see, a working hash function would be useful for me though, do you know some alternative?


it is working


what kind of hash do you need? you mean something like sha or md5?

Jan K19:10:41

I need to "snapshot" a nested collection into a scalar value, so I can cheaply compare these hashes without having the original collections


(I'm honestly surprised by how it hashes 1.2, that doesn't seem right)

Jan K19:10:21

Yes, it's super weird. The docs for "hash" say it's consistent with =, but look at this:

cljs.user=> (= (hash nil) (hash 0.2) (hash 0.9))


Any hash function with a 32-bit result, as Clojure/JVM does (I suspect ClojureScript does, too?) must have different input values that return the same hash value.


When someone says "hash is consistent with =", they mean "if (= x y) is true, then (= (hash x) (hash y)) is also true"


It does not mean that if (= (hash x) (hash y)) is true, then (= x y) is also true.


So if hash is consistent with =, and you find out that (hash x) and (hash y) are different, you can quickly conclude that (= x y) is false, but you cannot conclude anything from (hash x) and (hash y) being the same.

Jan K19:10:24

I see, so I guess the equality operator (=) does some further checks when the hashes are equal


I don't know the implementation of = in ClojureScript in detail, but it might not even use the hashes at all.


I know that in Clojure/JVM, = does not refer to the hash values at all.

Jan K19:10:35

The docs say equality checks are supposed to be cheap even for deep collections, I thought it must be using the hashes


The most common optimization there is to quickly check if two objects are the same object in memory. If they are, quickly return true without doing deep =, because deep = must be true.


yeah it first checks identical? always


With the implementation of Clojure's persistent data structures, suppose you have a map m1 with many keys and values. Then you create a map m2 that is the return value of (assoc m1 :x 5). If you do (= m1 m2), they are not identical, but large subtrees of their internal data structures will be.


So the deep = will start happening, but at various intermediate points will quickly return true when it finds subtrees that are identical objects in memory, which many of them will be.


If you create two maps m1 and m2 that are deep = "from scratch", with no common history, then deep = must go all the way down.


but it is common that deep trees being compared to each other share lots of sub-structures.

Jan K20:10:04

Thank you for explaining, makes sense. I was relying on hashes a bit too naively.


All of that said, the ClojureScript developers might consider it a performance bug that (hash 1.2) is equal to (hash 1.9), since that probably means there are way too many pairs of floating-point values that also have identical hash functions?


That would cause performance problems if one created large maps and/or sets with those values as keys.


For comparison, here is output from Clojure/JVM with version 1.10.1:

$ clj
Clojure 1.10.1
user=> (hash 1.2)
user=> (hash 1.9)

Jan K20:10:54

It seems all "hash" does for JS numbers is (js-mod (Math/floor x) 2147483647)


Given that lots of floating-point arithmetic gives approximate results, perhaps people do not often use approximate values as map keys or set elements terribly often -- it seems like trying to write an application like that would often lead to mismatches due to the approximations done in arithmetic.


Interestingly hash on a number used to be the identity function. Changed with