Fork me on GitHub
#clojurescript
<
2019-10-06
>
keymone01:10:40

why would adding datomic client library to project dependencies cause figwheel to freakout with java.lang.NoSuchMethodError: 'void com.google.common.base.Preconditions.checkState...?

plexus04:10:19

Probably pulling in an older version of some shared library

paulbutcher10:10:47

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

plexus10:10:50

@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!

thheller10:10:29

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

paulbutcher10:10:43

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.

paulbutcher10:10:16

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

thheller10:10:45

@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

thheller10:10:05

as all the electron tooling uses that ecosystem

plexus10:10:38

@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.

thheller10:10:59

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)

plexus10:10:08

yes, I know that

thheller10:10:58

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

plexus11:10:42

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

plexus11:10:52

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.

paulbutcher11:10:48

So I’m working through the instructions at https://rigsomelight.com/figwheel-main-template/ 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?

plexus11:10:53

> should be a CLJS namespace available on the classpath

plexus11:10:14

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

paulbutcher11:10:31

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]}

paulbutcher11:10:03

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

paulbutcher11:10:11

(I’ve not changed anything at all)

paulbutcher11:10:44

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

thheller11:10:45

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

paulbutcher11:10:23

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.

kwladyka13:10:50

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])
false
cljs.user=> (= (hash [1.2]) (hash [1.9]))
true
cljs.user=> (hash 1.2)
1
cljs.user=> (hash 1.9)
1

thheller19:10:41

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?

thheller19:10:17

it is working

thheller19:10:50

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

thheller19:10:02

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

andy.fingerhut19:10:35

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.

andy.fingerhut19:10:58

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

andy.fingerhut19:10:13

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

andy.fingerhut19:10:46

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

andy.fingerhut19:10:52

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

andy.fingerhut19:10:23

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

andy.fingerhut19:10:15

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.

thheller19:10:37

yeah it first checks identical? always

andy.fingerhut19:10:19

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.

andy.fingerhut19:10:21

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.

andy.fingerhut20:10:10

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.

andy.fingerhut20:10:40

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.

andy.fingerhut20:10:37

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?

andy.fingerhut20:10:44

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

andy.fingerhut20:10:24

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

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

Jan K20:10:54

It seems all "hash" does for JS numbers is (js-mod (Math/floor x) 2147483647) https://cljs.github.io/api/cljs.core/hash

andy.fingerhut20:10:59

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.

mfikes21:10:47

Interestingly hash on a number used to be the identity function. Changed with https://clojure.atlassian.net/browse/CLJS-435