This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-03-17
Channels
- # announcements (6)
- # babashka (2)
- # babashka-sci-dev (1)
- # beginners (74)
- # calva (3)
- # cider (33)
- # clj-kondo (19)
- # cljsrn (10)
- # clojure (75)
- # clojure-dev (11)
- # clojure-europe (39)
- # clojure-italy (1)
- # clojure-nl (1)
- # clojure-spec (4)
- # clojure-uk (6)
- # clojurescript (139)
- # code-reviews (8)
- # core-typed (7)
- # data-science (1)
- # docs (2)
- # emacs (11)
- # events (1)
- # introduce-yourself (8)
- # lsp (4)
- # malli (10)
- # off-topic (15)
- # pedestal (5)
- # podcasts-discuss (4)
- # polylith (18)
- # re-frame (6)
- # react (1)
- # reagent (18)
- # reitit (6)
- # releases (2)
- # rewrite-clj (1)
- # spacemacs (15)
- # sql (2)
- # vscode (5)
Hey, just wrote up a question on Clojureverse, and was wondering if any of you have tips on how to use Google’s <model-viewer>
with Reagent. https://clojureverse.org/t/googles-model-viewer-via-reagent/8737. Thanks 🙂
As far as I'm aware, what you describe are one of the reasons why custom elements aren't really taking off. It might be easy to just write something with them, but it's far from easy to develop with them, especially if you have to change the code of such a component.
You also write:
> without having to go with a custom solution via something like react-three-fiber
But how is using a custom element while requiring a custom <script>
better than using a library?
Conceptually, it's exactly the same. You will end with a React element that does something.
Ah what I mean by that is that <model-viewer> provides all the key functionality that I need. If I went with writing a custom solution I would have to write all of the functionality that I am getting already from <model-viewer> (ie, poster images, sane interface, AR compatibility and a great deal of edge case checks) It is possible for me to write all of those features on top of threejs but I would end up with an inferior version of all of them with the time that I have.
It is sad that they decided to so tightly couple this to the web component. Rather than have the web component be one way to interface with the broader feature set.
Indeed. Also, just as an additional piece of info against using it - it doesn't support Safari and Firefox.
Only for web-xr features
Which is an issue with the larger ecosystem
I’ve been developing this in firefox. Safari doesn’t support webxr at all on the mobile side.
Are there any other ways to try to integrate this beyond the two I mentioned?
An <iframe>
, unless that's what you mean by "a separate html element from the Reagent controlled application".
Maybe it's possible to tear apart the library and use it without having to rely on the custom elements machinery.
By the separate element I meant a manually created element outside of the “app” hierarchy mounted by reagent. Thanks for the tips.
Dumb question... when writing an async loop, do I have to use loop/recur or for/doseq is also fine? I found one random SO answer that claims doseq is fine but for is not because it's lazy, but my code did not work with doseq so I'm just making sure that I can rule that out as a problem
Anything should be fine if you use it correctly and in the right context.
It's better to ask a more concrete question here. Like "why does my async code not work with doseq
", while also providing the code itself. :)
Yea I'll post it if there is anything I can't solve. For now it's probably simple bugs that are more work to provide the needed context than to debug. Just wanted to know what constructs are fine to use in theory.
As far as I'm aware, the only things that can definitely result in problems are interop-related. In particular, ^js
disappears in go
blocks. But there are workarounds.
Works! Indeed turns out my bug was some destructuring, and then some thing that wasn't sorted, etc. Doseq works fine.
for a reframe electron app, does any jvm/java code run, or is OS io done through node / cljs calls?
Are there any good tutorials out there for using clojurescript interop with javascript promises? I'm trying to use it with showOpenFilePicker() which returns a JS promise, but all i seem to be able to do with the return so far is to generate console errors.
Thank you. I had found that earlier, but had not been able to get it to work. I finally got it working with the code:
(defn ld-file
[]
(-> (. js/window showOpenFilePicker)
(.then #(.getFile (first %)))
(.then #(.text %))
(.then #(println %))))
Thank you again.I have a situation where some dead code isn't being eliminated with :optimizations :advanced
. Specifically, a whole lot of code ends up getting pulled in because of a situation like D
below:
(def A {"This is eliminated" "This is eliminated"})
(def B (f "This is eliminated"))
(def C {nil (f "This is eliminated")})
(def D {(f "This is _NOT_ eliminated") nil})
The resulting code doesn't contain any references to D
, but it does contain a 'bare' statement that creates the map (`{(f "This is NOT eliminated") nil}`). It doesn't seem to matter what f
is — I get the same result if I replace it with identity
.
Any ideas what might be causing this? Is there anything I can do to help the compiler get rid of code like this?@wombawomba probably not, it might be a case that Closure Compiler can't handle
assuming f
is free of side effects, you could probably create an example where this fails for plain JavaScript and request an enhancement
Alright, thanks. Yeah it doesn't seem to matter if there are side effects or not (I'm assuming identity
doesn't have any side effects even when compiled)
Let's say I want to boil this down to a minimal JS example. Is there a way to compile with advanced optimizations but without dead code elimination, and then run the code elimination pass separately?
hmm... I wouldn't think it's that simple
I mean sure, the code generation is, but this issue might not be.
Right, yeah. I'm thinking more about that this issue only seems to arise in some cases (`{(identity 1) 2}` but not {1 (identity 2)}
). The code gets compiled to:
PersistentArrayMap$createAsIfByAssoc(["This is _NOT_ eliminated", null]);
(which doesn't get eliminated), vs:
PersistentArrayMap$createAsIfByAssoc([null, "This is eliminated"]);
(which does get eliminated)The problem seems to only (and consistently) occur when there's a key in a map that gets created via a (any) function call.
...so I'm assuming that this is due to something specifically with how createAsIfByAssoc
is defined
Is there some caching/interning involved for map keys maybe?
(i.e. a side effect)
that is true, for strings, keywords, symbols I think for hashing we may intern (if we can't figure out the hashes at compile time)
Alright, so assuming that intering is what's causing this, could there be a way to work around it?
Hmm... audit how?
like look at what createAsIfByAssoc
does in the standard library - I scanned it but I didn't see anything particular suspicious
ah okay
yeah that's what I'm doing right now
I also can't see that it's doing anything especially weird.
yeah, so then I would try to make something minimal - it's not obvious to me why the values work but not the keys
you also might want to try master, a few things were changed to align w/ Clojure 1.11 in this code path
looking at the generated JS, the only thing that seems to differ between keys and values is that it calls cljs$core$array_index_of$
for the keys
...although that function is a bit more complicated, so perhaps it's what's causing problems for Closure
Is there a nightly build or something I can use to test master, or do I need to build it myself?
I'm not, but either way I went ahead and compiled it on my own
Unfortunately, the changes on master don't seem to help.
hrm that seems harder to explain then, since I don't think array-index-of
is in the path
yeah it doesn't seem to be there in the compiled code
hmm... afaict the emitted createAsIfByAssoc
is actually pretty complex
it seems to walk through the code and call cljs.core/key-test
to check for identical keys
it maybe something about the key testing is thwarting Closure - but then this seems to point at a Closure issue
I'll try removing the key check and see if that fixes it.
Yup, replacing createAsIfByAssoc
with a direct call to PersistentArrayMap.
fixes the problem.
right only option is to request some kind of enhancement to Closure - there's really nothing else we could do
Makes sense. I'll try to see if I can trick Closure into doing the right thing, and if not, I guess I'll have to try to repro in plain JS and file a ticket with Closure
pretty sure this comes down to equality semantics that are too complex for closure to "understand". there is also a path that may throw so that alone probably stops the elimination
although this might warrant some investigation. the only path that may throw is the cljs.core/missing-protocol
. but since it has a default implementation it will never reach that, so in theory could become somewhat more pure?
but I doubt it'll understand protocols such as IEquiv
fully so it probably ends there
I'm trying to see if that could be it. Is there any way to do a regular js equality check in cljs to confirm?
Yeah that does the trick.
So I guess the conclusion then is that there's not really anything that can be done about this.
...if, in theory, I wanted to work around this for my project, would it be possible for me to alter-var-root!
or set!
createAsIfByAssoc
before any of my code gets compiled? 😇
what are you trying to do in the first place? I mean what is this map that you are trying to get rid of and why?
@wombawomba you going to have be very careful - honestly I don't see how you wouldn't just break everything
eliminating these maps also doesn't seem interesting unless you're getting rid of more than 10% of your code by doing so
@thheller So I'm using https://github.com/plumatic/schema — and I'm defining a whole bunch of schemas in different places (like (def MySchema {(schema.core/optional-key :foo) schema.core/Str})
) — and I want to compile a release build that's completely stripped of all schema definitions except those I actually use (I do (schema.core/check MyOtherSchema x)
in a few places).
@dnolen this stuff seems to account for ~30% of my build size
then you have to go back to the first point - I don't see how you won't break your deps
(by "those I actually use", I mean in this specific build — the schemas are mostly there to check fn inputs/outputs in development)
@dnolen why not? 🙂 Duplicate keys normally cause an error, no? So if I'm not seeing any such errors, then shouldn't I be able to just get rid of the duplicate key check?
I'd only have broken maps if I actually try to create maps with duplicate keys though?
Yeah sure
...actually, I'm creating all these schemas using a macro anyway — would it be possible to disable the check just for the scope of that particular macro?
Yeah I suppose
Although, I'd rather avoid having to do a 'deep' rewrite of the code in the macro. Would is be possible to use something like with-redefs for this purpose?
(also, if I wanted to go down that path, could I do something like that for createAsIfByAssoc
?)
or rather I'm super skeptical that Closure will make sense of these redefinitions - effectively runtime mutation
@wombawomba instead of (def MySchema {(schema.core/optional-key :foo) schema.core/Str})
you could (dev-only-def MySchema {(schema.core/optional-key :foo) schema.core/Str})
macro that just emits the code in a way that can be eliminated. eg (def MySchema (when ^boolean js/goog.DEBUG {(schema.core/optional-key :foo) schema.core/Str}))
@thheller yeah I've actually built something like already, but it ends up being kind of clunky having to keep track of which schemas to annotate
another option is moving all the dev only schemas to a dedicated namespace that is only included via preloads. ie. only in dev
depending on how you use all the schemas delay
may also work. (def MySchema (delay {(schema.core/optional-key :foo) schema.core/Str}))
. just requires using @MySchema
whereever you would actually use it
I can't really move the definitions — a lot of them are part of shared libraries used in multiple clj and cljs projects
delays are potentially interesting... are you saying that anything wrapped in a delay won't be included in the compilation output unless it's actually used?
I'm already defining my own versions of schema.core/def
, schema.core/defn
etc, so I suppose I could just make them work with delays transparently
last time I checked delay
is eliminated if never deref'd regardless of what is in it
could there be some automated way to figure out which schemas I need to emit?
...actually, it seems like there's a Closure annotation (https://github.com/google/closure-compiler/wiki/Annotating-JavaScript-for-the-Closure-Compiler#nosideeffects-modifies-thisarguments) that could be used to mark functions as side effect-free. Couldn't this be used to help the Closure compiler eliminate the problematic code?
I'm attempting to read and write files in clojurescript by transliterating the code I found in https://web.dev/file-system-access/. The translteration of the file read code was somewhat simple, and using the pointers in https://clojurescript.org/guides/promise-interop, I was able to get it working fairly easily. However, writing seems more involved. I am translating the following code:
async function writeFile(contents) {
const handle = await window.showSaveFilePicker();
// Create a FileSystemWritableFileStream to write to.
const writable = await handle.createWritable();
// Write the contents of the file to the stream.
await writable.write(contents);
// Close the file and write the contents to disk.
await writable.close();
}
According to the advice in the promise-interop guide, this should translate to (something like):
(defn save-file
[data]
(let [writeable (-> (. js/window showSaveFilePicker)
(.then #(.createWritable %)))]
(.then (fn [writeable]
(-> (.write writeable data)
(.then #(.close writeable)))))))
However, when I try to run this, I get the following error:
./cljs-runtime/atlas.complexes.js:472 Uncaught TypeError: (intermediate value).then is not a function
at Object.atlas$complexes$save_file [as save_file]
I'm pretty sure that something in my translation of the close is messed up. Anyone have any ideas as to where my translation is going wrong and how to right it?Your second .then
is not called on a promise with a function as an argument. Instead, you call it on the (fn [writable] ...)
function, with no arguments.
(-> (.showSaveFilePicker js/window)
(.then (fn [^js handle]
(.createWritable handle)))
(.then (fn [^js writable]
(-> (.write contents)
(.then (fn [_]
(.close writable)))))))
or something like that.I messed about with it a little bit. It turns out that this is the magic formula:
(-> (.showSaveFilePicker js/window)
(.then (fn [^js handle]
(.createWritable handle)))
(.then (fn [^js writable]
(-> (.write writable contents)
(.then (fn [_]
(.close writable))))))))
Thanks for your help!