clojurescript

ClojureScript, a dialect of Clojure that compiles to JavaScript http://clojurescript.org | Currently at 1.10.879
2021-02-03T01:14:09.340Z

hi, is it possible to do cljs development without node? any good framework that doesn't have node dependency? any wasm based framework?

2021-02-03T02:03:46.340400Z

I am far from an expert on this topic, but you can do cljs development using a web browser of your choice to execute the JavaScript code output by the cljs compiler, without Node.

2021-02-03T02:05:32.340600Z

If you mean development running on a host, not a web browser, there are multiple choices of JavaScript runtime environments besides Node that can be used, but I have no idea if that is practical in terms of libraries and host access they provide.

2021-02-03T04:42:44.340900Z

thanks... i saw fulcro and reagent... seems to be popular but both need node.js...

2021-02-03T04:44:31.341100Z

would be nice to have isomorphic clojure without node dependency... like blazor for c# or bolero for f#...

2021-02-03T05:19:46.344700Z

Hello. I would like to run a macro which analyzes the body of each def -ined expression in a CLJS program via my own macro. Is there a way to tweak the Clojurescript compiler to redefine def during compilation without modifying the source code to be analyzed?

2021-02-04T03:29:34.414200Z

> Yes but some build tools don’t support custom readers for CLJS. @p-himik Which tool did you have in mind?

p-himik 2021-02-04T03:34:27.414400Z

Shadow-cljs

👍 1
p-himik 2021-02-04T03:51:25.415300Z

Couldn't find it in the documentation but here's a reference from the relevant issue: https://github.com/thheller/shadow-cljs/issues/272#issuecomment-386865049

2021-02-03T05:21:25.345200Z

Or is there any other tool which I could use to run such analyzis?

2021-02-03T05:25:18.345300Z

What I want to do is to find the CSS classes used in a codebase. I would be happy to skip the CSS classes on render functions which would be removed by dead-code elimination, that’s why I am thinking about using the CLJS compiler.

2021-02-03T06:38:38.345500Z

Here is the related Github issue: https://github.com/green-coder/girouette/issues/28 I might try to solve it using clj-rewrite at first, and see later what I can do w.r.t. dead code elimination.

2021-02-03T06:40:12.345900Z

If the CLJS compiler can emit a list of fully qualified symbols which are dead code, that would be already very useful.

p-himik 2021-02-03T07:26:46.346100Z

How does Reagent need node.js? I'm not familiar with Fulcro but I have doubts it requires it as well.

p-himik 2021-02-03T07:27:37.346300Z

I have two projects that use Reagent with CLJS on the frontend and CLJ on the backend. Node.js is only used to install NPM dependencies. There's no trace of it in the runtime.

p-himik 2021-02-03T07:32:12.346500Z

Is it tied to Reagent? If not, how would you determine what's a class and what's not?

p-himik 2021-02-03T07:33:19.346700Z

Also, just in case - it's impossible to determine all classes during compilation time. Even some code as simple as this one will make it impossible:

(def view [class]
  [:div {:class class} "Hello"])

2021-02-03T07:33:21.346900Z

It is not tied to reagent. My current idea is described in the Github issue, it is based on annotations (meta data)

p-himik 2021-02-03T07:34:36.347200Z

But it annotates Hiccup. And in particular, it seems to use the extension that Reagent has introduced on top of Hiccup - in particular, being able to pass a collection to :class.

2021-02-03T07:35:59.347600Z

In your example, the annotation should be added at every place the user types the name of a class.

(let [my-classes ^:css ["class1" "class2"]]
  [view my-classes])

2021-02-03T07:39:30.347900Z

The ^:hiccup is for convenience when using hiccup, but the same can be done using only ^:css

2021-02-03T07:40:50.348100Z

Ideally, if I can find a tool that rewrites a Clojure(Script) code base without the dead code, my problem is resolved.

p-himik 2021-02-03T07:41:15.348400Z

Ah, I see. Still, (str "color-" (if blue? "blue" "red")) won't work. :) In any case, you don't need to create a custom macro for def at all. Especially given that such metadata can be outside def:

(let [classes ^:css ["class1" "class2"]]
  (def x [] [:div {:class classes}]))
You can do it with a custom compilation step that just reads the CLJS/CLJC files as EDN and finds all elements with the right metadata. Two issues that I can see right now though: - You cannot put metadata on keywords and strings. So users will have to wrap all single-keyword or single-string classes in a vector - In the issue you mention ^:css (conj ["ml-2"] :py-2) - how would that even work? Assuming conj can be any function whatsoever

👍 1
p-himik 2021-02-03T07:42:58.348600Z

What do you mean by "rewriting CLJ(S) code"? What exactly do you need to rewrite? Is it only for the second item in the "Nice to have" section in that issue?

2021-02-03T07:43:19.348800Z

I must be greedy 🙂

2021-02-03T07:45:31.349200Z

As long as the user can write the metadata in front of the strings (which is the case), we can possibly have a tool which parses and finds it from the source code.

p-himik 2021-02-03T07:46:07.349400Z

I don't want to dissuade you, but I genuinely think that what you want is unachievable.

cljs.user=> (def a ^:hello "world")
Metadata can only be applied to IMetas

p-himik 2021-02-03T07:46:26.349600Z

You just cannot determine everything in compile time.

p-himik 2021-02-03T07:47:08.349800Z

I mean, without putting some constraints on users. Like not composing strings and not using conditional classes.

2021-02-03T07:47:15.350Z

oh no ... Maybe a literal reader would work then. #css "my-class" ?

p-himik 2021-02-03T07:47:48.350200Z

Yes but some build tools don't support custom readers for CLJS.

2021-02-03T07:49:25.350400Z

As a last option, maybe a do-nothing macro can be used as a way to annotate things. (girouette/css "my-class")

p-himik 2021-02-03T07:50:00.350600Z

Yes, I was in the process of writing about that. :) This way, it will even be possible to create class names dynamically if a user makes sure that the function/macro is used for every class name that will be used in runtime. Still, you won't be able to use DCE.

2021-02-03T07:53:31.350800Z

Thank you for all your feedback @p-himik, that's very helpful.

👍 1
Yehonathan Sharvit 2021-02-03T07:55:02.351100Z

You mean a function that parses a JSON string or a function that parses the result of JSON.parse?

p-himik 2021-02-03T07:55:21.351300Z

The latter.

p-himik 2021-02-03T07:55:53.351500Z

I wouldn't implement my own JSON.parse - it's highly optimized in browsers, you just can't get on that level of performance from the JS level.

orestis 2021-02-03T08:30:52.352700Z

Can I mark an argument in ClojureScript as deprecated, so that the compiler will pick it up and flag it?

p-himik 2021-02-03T08:34:02.352800Z

I don't know the answer but I'm curious - what does "deprecating an argument" mean?

2021-02-03T08:39:10.353400Z

I think Hickey’s answer would be to create a new function and leave the old as is

2021-02-03T08:39:54.353600Z

although, phrased more eloquently

orestis 2021-02-03T08:45:11.353800Z

Well that argument doesn’t hold well in an internal codebase 😄 Ideally I’m looking for a good “find all references” tool and the compiler is at least exhaustive 😉

orestis 2021-02-03T08:45:37.354Z

I should have rephrased, the argument is in a :keys destructuring so it’s a little bit more involved.

orestis 2021-02-03T08:45:58.354600Z

Actually what about deprecating a var? Is there some metadata I could assign there?

p-himik 2021-02-03T08:50:26.354700Z

Ah, OK, I see now. So technically it's not an argument at all, it's just a result of destructuring. :) It's just not possible. The compiler will not be able to do anything in this scenario, for example:

(let [args {:a 1}]
  (my-fn args))

p-himik 2021-02-03T09:08:09.355100Z

It's possible for functions:

cljs.user=> (defn ^:deprecated x [])
#'cljs.user/x
cljs.user=> (x)
WARNING: cljs.user/x is deprecated at line 1 <cljs repl>
nil

p-himik 2021-02-03T09:09:16.355300Z

And protocols:

cljs.user=> (defprotocol ^:deprecated P)
false
cljs.user=> (defrecord R [] P)
WARNING: Protocol P is deprecated at line 1 <cljs repl>
cljs.user/R
Can't find anything else in the CLJS code.

p-himik 2021-02-03T09:10:02.355500Z

Interestingly, CLJS itself does not use the metadata but rather specifies the deprecation just in docstrings or sometimes even in regular comments.

Helins 2021-02-03T09:35:46.359400Z

I found a couple posts about creating web components in CLJS. I am a bit surprised that they are not more popular, is there any particular reason? Looks like an interesting match for React given that CLJS could compile hiccup to efficient web components eschewing "a lot" of vdom diffing. (@thheller that was occuring to me while having a quick look at Arborist)

2021-02-03T09:40:09.359500Z

Can you share the posts, please?

thheller 2021-02-03T10:38:42.360300Z

as for web components they are kind of tedious to work with and don't fit well into the whole REPL/hot-reload world we live in now. They do solve some issues but in turn create others, basically its a mess 😛

Helins 2021-02-03T11:20:18.360500Z

Indeed, all that. @thheller I admit I haven't toyed properly with web components so I am very naive (and should do so before starting an involving conversation). The issue I see with a react-based workflow is how to hot-reload a webcomp whose template has changed. Is there any other obvious issue?

thheller 2021-02-03T11:21:33.360700Z

that is the web components concern and where this fails entirely

thheller 2021-02-03T11:21:57.360900Z

as far as react or alternatives are concerned web components are just DOM elements, just like any other div

thheller 2021-02-03T11:23:51.361300Z

IMHO web components don't really solve any practical issues. guess thats why they aren't more popular still.

Helins 2021-02-03T11:27:22.361500Z

Right, but one solution could be at dev time to generate a new tag for a web component everytime its template has changed, forcing react to re-render the whole thing (hence the updated template). Absolutely no idea how practical that would be though. But at production, I can envision interesting optimizations (eg. fast native cloning Vs creating each element of a template from JS ; React maintaining only one virtual node since a whole template is indeed seen as a single element).

thheller 2021-02-03T11:29:02.361700Z

dunno what you mean by template. lit-html like cloning is about as fast as react and doesn't matter in practice

thheller 2021-02-03T11:31:02.362Z

dunno I'm done with react and wouldn't use web components as an alternative either. my stuff is sort of inbetween all of it and I like it:P

Helins 2021-02-03T11:35:10.362200Z

Working with <template> But yeah, your work is really interesting (although I didn't have much time to read it in greater details). It's just it makes me wonder about how some ideas (such as maintaining several DOM nodes under one "virtual" node) could be applied to React, assuming the world is not ready to let it go. Who knows, maybe if I find time some day to actually mess with this...

thheller 2021-02-03T11:38:06.362400Z

something has to maintain those nodes and react rarely is the issue

thheller 2021-02-03T11:38:33.362600Z

well react can be the issue. depends on how it is used I guess 😛

👍 1
Marcus 2021-02-03T11:49:21.365700Z

Hi all! What would be the best approach to introduce ClojureScript in an existing vue.js application?

thheller 2021-02-03T11:53:22.366400Z

what should the CLJS parts handle? if you want the "view" code in CLJS then that really can't be done without replacing vue entirely.

Marcus 2021-02-03T12:01:22.368900Z

@thheller the long term goal would be to replace vue.js entirely. But as this is not doable at once, there would need to be some kind of interoperation I think.

thheller 2021-02-03T12:06:07.370100Z

yeah thats difficult as most of vue is compiler based. it is possible but not sure anyone has done it before or wrote a library to make it easier.

Marcus 2021-02-03T12:07:32.370500Z

I found the glue library, but it seems abandoned

borkdude 2021-02-03T14:49:02.373Z

I found an interesting implementation detail difference about Clojure and CLJS. In Clojure:

user=> (defn foo [n] (= (range n) (keys (zipmap (range n) (range n)))))
#'user/foo
user=> (foo 8)
true
user=> (foo 9)
false
This shows that hash-maps have a different ordering than array-based maps (the threshold is at 8 kvs). But in CLJS foo returns true for up to 32. So is there a magic number 64 somewhere in CLJS or is this due to how something in JS works differently?

thheller 2021-02-03T14:56:16.374100Z

my guess is that it is related to how numbers are hashed in CLJS. the array map threshold is 8 as well. probably same as https://clojure.atlassian.net/browse/CLJS-3297

alexmiller 2021-02-03T14:58:08.375Z

hash maps are unordered and thus that ordering can change at any time (and may not be the same between CLJ and CLJS)

borkdude 2021-02-03T14:59:32.375900Z

@alexmiller sure, I was just interested in the why, I realize this is just an impl detail one should never rely on

borkdude 2021-02-03T15:00:36.376700Z

I remember one project where someone built a datomic query lib on top of hash-maps. And then we upgraded to clojure 1.6 and suddenly things became slow for queries with more than 8 clauses, :P

borkdude 2021-02-03T15:01:17.377200Z

Quickly rewrote that to tuples instead

dgb23 2021-02-03T15:02:13.377600Z

oh wait a second, does that mean that keys != keys of the same map?

dgb23 2021-02-03T15:02:37.378200Z

map = map -> keys = keys?

dgb23 2021-02-03T15:03:14.378500Z

(sorry that’s a beginner question)

2021-02-03T15:03:43.379Z

the sequence of keys is compared, which is false when the keys are not in the same order.

dgb23 2021-02-03T15:05:06.380Z

so it has to be (if at all): map = map -> set of keys = set of keys

borkdude 2021-02-03T15:05:24.380400Z

@denis.baudinot I think you can rely on keys always returning the same order with the same map, given the same version of clojure

alexmiller 2021-02-03T15:05:37.380800Z

yes (for a particular instance of the map)

Mno 2021-02-03T15:06:02.381700Z

if you turn it into a set, then yeah it should be true regardless of order, right?

👍 1
dgb23 2021-02-03T15:06:03.381900Z

but if I create two maps independently I don’t have that property

borkdude 2021-02-03T15:07:20.382800Z

So the question is, are there cases for which (= m1 m2) and (not= (keys m1) (keys m2))? I always assumed no

alexmiller 2021-02-03T15:07:58.383400Z

there is no guarantee for that

alexmiller 2021-02-03T15:08:17.384100Z

if (identical? m1 m2) then (= (keys m1) (keys m2))

dgb23 2021-02-03T15:08:28.384400Z

@borkdude you proved that this doesn’t always hold with your example above ( with range …)

borkdude 2021-02-03T15:09:02.385100Z

@denis.baudinot No, those examples were about the ordering of the keys changing due to not basing the impl on arrays

dgb23 2021-02-03T15:09:17.385300Z

right!

borkdude 2021-02-03T15:09:48.386200Z

but TIL that you can not rely on the keys property in general if it's not the same object

borkdude 2021-02-03T15:10:20.387400Z

I don't think I've ever been bitten by this but good to know and it kind of makes sense too

alexmiller 2021-02-03T15:10:35.388Z

the only guarantee with keys is that the order of (seq m) (keys m) and (vals m) is the same (they are all in seq order for an instance)

alexmiller 2021-02-03T15:10:58.388900Z

so (= m (zipmap (keys m) (vals m)))

dgb23 2021-02-03T15:11:02.389100Z

well keys “should” return a set, because the keys of a map is a set. But I’m sure there are reasons for that it doesn’t.

borkdude 2021-02-03T15:11:19.389800Z

oooh right, (= m (zipmap (keys m) (vals m))), that is the property I was thinking of and I rely on this all the time

alexmiller 2021-02-03T15:11:20.389900Z

well, disagree with "should"

dgb23 2021-02-03T15:11:36.390400Z

that’s why I put it in quotes!

alexmiller 2021-02-03T15:11:41.390600Z

that's not what it's defined to be

borkdude 2021-02-03T15:12:04.391Z

one reason it's not is probably performance: coercing a seq into a set isn't free

borkdude 2021-02-03T15:12:20.391300Z

and also the above property wouldn't hold anymore

borkdude 2021-02-03T15:12:30.391600Z

since sets are not ordered

dgb23 2021-02-03T15:12:43.391900Z

oh right!

borkdude 2021-02-03T15:12:46.392100Z

(the property (= m (zipmap (keys m) (vals m))) I mean)

dgb23 2021-02-03T15:13:00.392400Z

good thing I’m not a language designer

Mno 2021-02-03T15:13:10.392800Z

(yet)

☝️ 2
😄 2
2021-02-03T15:14:27.393800Z

Why would we expect that property to hold in the first place?

dgb23 2021-02-03T15:15:19.394100Z

the transitive equality on keys or the zipmap one?

borkdude 2021-02-03T15:15:52.394500Z

the zipmap one is important in postwalk-like code

borkdude 2021-02-03T15:17:51.395800Z

It would be interesting to research the other (non-guaranteed) property using generative testing maybe. I leave this as an exercise for the reader.

2021-02-03T15:18:32.396300Z

@borkdude There are such maps. See this article for a REPL session that shows how to construct one in Clojure. hash is different in cljs, so while there should be similar examples for cljs, it won't be exactly the same example: https://github.com/jafingerhut/thalia/blob/master/doc/other-topics/referential-transparency.md#example-3-interaction-between-clojures-hash-sets--and-seq

2021-02-03T15:18:35.396600Z

The original one (= (range n) (keys (zipmap (range n) (range n)))) is what I was thinking of

borkdude 2021-02-03T15:19:33.397800Z

@robert.mitchell36 That wasn't a property that should hold at any time, but an implementation detail I noticed that was different for a certain n in Clojure and CLJS. The n in CLJS is much higher and I wondered why.

👍 1
2021-02-03T15:20:21.398600Z

cljs hash on integers today is (hash i) -> i for a lot of 'small' integer (probably up to 2^32 or so). That is not true in Clojure since 1.6.0

borkdude 2021-02-03T15:21:55.399500Z

Thanks! I kind of expected an insightful reply from you :)

borkdude 2021-02-03T15:23:01.400300Z

Thank you for being part of today's nerd snipe. https://xkcd.com/356/

2021-02-03T15:23:25.400600Z

It turns out that Haskell developers have healthy arguments over whether hash maps should even have a function that lists the keys in a particular order, because it violates the property (x = y) implies (toList x) == (toList y) where x,y are hash maps.

2021-02-03T15:24:16.401300Z

It used to be that (hash i) -> i in Clojure 1.5.1 and earlier, but that leads to many hash collisions on short vectors of integers, e.g. grid coordinates with integer x and y. I created this issue recently with REPL examples in Clojure 1.5.1, 1.6.0, current cljs, and proposed patched cljs, showing the scenario and difference in hashing behavior. https://clojure.atlassian.net/jira/software/c/projects/CLJS/issues/CLJS-3297

dgb23 2021-02-03T15:25:56.401500Z

well thanks I read the title text…..

2021-02-03T15:26:17.401700Z

Well, maybe "spirited arguments", not sure they are all healthy 🙂

borkdude 2021-02-03T15:26:49.401900Z

lol. in clojure we just take what we can get, I guess ;)

2021-02-03T15:27:54.402100Z

I think the argument comes down to "useful to have, despite violating that useful property" versus "really, really important never to violate that property, and willing to give up useful functions in order not to violate it".

dgb23 2021-02-03T15:28:33.402400Z

no, i have to bookmark it and move on!

2021-02-03T15:57:40.403100Z

Looks like it’s (hash n) -> (Math/floor n) for all numbers < (2^31 - 1), which has the interesting consequence that (hash 1.5) = (hash 1): https://github.com/clojure/clojurescript/blob/fa4b8d853be08120cb864782e4ea48826b9d757e/src/main/cljs/cljs/core.cljs#L1012

2021-02-03T16:27:59.403400Z

I didn’t find anything that definitely explains the difference, but I did find that the hashmap threshold is different in clj & cljs (not sure if that has any impact at all): https://github.com/clojure/clojure/blob/140ed11e905de46331de705e955c50c0ef79095b/src/jvm/clojure/lang/PersistentArrayMap.java#L33 https://github.com/clojure/clojurescript/blob/fa4b8d853be08120cb864782e4ea48826b9d757e/src/main/cljs/cljs/core.cljs#L7005

borkdude 2021-02-03T16:29:59.403800Z

I think in CLJS they count the keys but in clojure they count the keys + values, so it's probably the same threshold

takis_ 2021-02-03T18:18:13.404600Z

Hello,in Clojure we seperate Java/Clojure sources in different directories and say where to find each sources to leinengein,but they are part of the same project. For a project Clojurescript/Nodejs mixed how it works? They have to be separate projects,and be like npm depedencies? Or can be made as 1 project also?

p-himik 2021-02-03T18:23:04.404800Z

> in Clojure we seperate Java/Clojure sources in different directories I don't. :) Frankly, I don't see any point in separating files by language/extension. Same with JS - I'm pretty sure at least shadow-cljs allows you to import a JS file from anywhere within the project. Probably other build tools as well.

takis_ 2021-02-03T18:25:31.405Z

i do it like that in clojure, i use leinengein

takis_ 2021-02-03T18:26:28.405200Z

i know that i can use a npm package from clojurescript , but how to use a single js file ?

takis_ 2021-02-03T18:28:44.405600Z

i want a mixed project , with clojurescript and js files

p-himik 2021-02-03T18:28:56.405800Z

It may be just a requirement of lein or one of its plugins. Or maybe you can just specify the same path for both keys, I don't know. I don't use lein, I can't really tell. Just add that JS file along with the CLJS files and require it, something like:

(ns my.proj.cljs-ns
  (:require ["my/proj/js-file.js" :as js-file]))
Whether it will work or not depends on your build tool.

p-himik 2021-02-03T18:30:09.406Z

Ah, right - some details here: https://shadow-cljs.github.io/docs/UsersGuide.html#classpath-js

takis_ 2021-02-03T18:30:14.406200Z

ok thank you,but from a js file ?

takis_ 2021-02-03T18:30:22.406400Z

how to use a clojurescript file

p-himik 2021-02-03T18:30:32.406600Z

Ah, you want to embed CLJS in JS?

takis_ 2021-02-03T18:30:54.406800Z

i want a mixed project like i used to do in clojure

takis_ 2021-02-03T18:31:06.407Z

i had both .java and .clj files in 1 project

p-himik 2021-02-03T18:31:15.407200Z

Do you plan on embedding JS code into CLJS one or CLJS code into JS one?

takis_ 2021-02-03T18:31:27.407400Z

both

p-himik 2021-02-03T18:31:27.407600Z

Or both?

p-himik 2021-02-03T18:31:51.407800Z

Right. In that case, you will have to use the ^:export metadata and access CLJS symbols via the resulting globals.

p-himik 2021-02-03T18:32:20.408Z

An example can be seen here: https://shadow-cljs.github.io/docs/UsersGuide.html#_working_with_optimizations

p-himik 2021-02-03T18:32:41.408200Z

So use the first link to use JS from CLJS and the second to use CLJS from JS.

takis_ 2021-02-03T18:32:48.408400Z

ok thank you for your help i am new i will try the first, use javascript from clojurescript and see

takis_ 2021-02-03T18:32:57.408600Z

ok i go for it 🙂 thank you

p-himik 2021-02-03T18:33:19.408800Z

Alternatively, create your internal APIs in such a way so that you can simply use JS from CLJS and just pass all the relevant CLJS objects directly to the JS functions. No problem.

thheller 2021-02-03T21:25:19.409600Z

.js code in shadow-cljs can use import { something } from "goog:some.cljs.namespace" to access CLJS code

thheller 2021-02-03T21:25:33.409800Z

that would get (def something "foo") from that ns

thheller 2021-02-03T21:25:57.410Z

and CLJS code can just to required as linked above

thheller 2021-02-03T21:26:00.410200Z

works both ways, just shouldn't be circular if you can avoid it