squint

borkdude 2024-11-01T16:05:11.413629Z

I just had a wild (but maybe too magic) idea again. What if we coerced Atoms into Promises and compiled @ to just (cljs.core/deref ...) outside of async contexts and to js-await inside async contexts. Then we could write (let [resp @(js/fetch "") txt @(.text resp)] txt) when dealing with promises but also keep around compatibility with atoms

👀 1
❤️ 4
john 2024-11-01T19:44:21.144639Z

That's what I did in cljs-thread

john 2024-11-01T19:44:38.249149Z

Though probably not exactly like that

john 2024-11-01T19:46:08.030999Z

Well, it kinda worked for promises circumstantially https://github.com/johnmn3/cljs-thread/blob/master/src/cljs_thread/sync.cljs#L76

john 2024-11-01T19:47:47.624349Z

So that code on the main thread would still work with similar formalisms being used on worker threads, I made promises handle derefable

john 2024-11-01T19:48:24.103219Z

I might have had to do

(extend-type js/Promise
  ICloneable
  (-clone [p] (.then p)))

john 2024-11-01T19:48:26.888319Z

to get it to work

john 2024-11-01T19:52:01.598459Z

hmm, so with your impl, if we call @(fetch in a function that is not async, but is called inside an async function, that wouldn't work right?

john 2024-11-01T19:53:01.050179Z

Just wonder if like, the compiler can just know somehow whether we're in an async context or not, and do the right thing depending

john 2024-11-01T19:53:35.914309Z

It's definitely magic-y

john 2024-11-01T19:54:41.468309Z

Wait, how does your impl know which context it's in?

borkdude 2024-11-01T19:59:30.088769Z

if we call @(fetch  in a function that is not async, but is called inside an async function, that wouldn't work right?
this is correct, but js-await also doens't work in that case, you need to mark it as async

borkdude 2024-11-01T19:59:49.971859Z

the compiler knows

borkdude 2024-11-01T20:00:22.177849Z

I read @ to a special form and then the compiler either translates that to deref or js-await based on if we're inside an async function

john 2024-11-01T20:06:05.574659Z

Ah it's a dynamic binding *async*

john 2024-11-01T20:06:25.630989Z

That's awesome you can do it at compile time

john 2024-11-01T20:06:50.284909Z

Wait...

john 2024-11-01T20:07:28.726619Z

How does it know to do a normal deref, in the event the callee is actually an atom that wants to be derefed?

borkdude 2024-11-01T20:08:08.900679Z

because I made the atom type be a promise as well

borkdude 2024-11-01T20:08:17.672439Z

see commit

borkdude 2024-11-01T20:09:21.811509Z

so (js-await (atom 1)) would return 1

john 2024-11-01T20:09:33.860029Z

ah I get it

john 2024-11-01T20:13:42.238619Z

Yeah I don't see the downsides. feels to me like it was meant to be

borkdude 2024-11-01T20:20:01.525759Z

the only downside might be performance or so

borkdude 2024-11-01T20:20:16.440789Z

but you can always write a manual (deref ..) is that is a concern

john 2024-11-01T20:28:13.178029Z

Is it possible to make an anonymous function ^:async? I was just trying to test nested async fns

borkdude 2024-11-01T20:32:06.702239Z

yes:

^:async (fn [])

borkdude 2024-11-01T20:33:04.086519Z

(^:async fn []) also works

borkdude 2024-11-01T20:33:34.197159Z

that's probably the preferred syntax since in JVM Clojure we have: (^:once fn* [] x)

john 2024-11-01T20:36:18.498939Z

Should this work?

(defn ^:async gettext []
  (let [res ^:async (fn [] @(js/fetch ""))
        json2 (-> @(.json (res)) (dissoc :headers))]
    (set! js/app.innerHTML #html [:pre (js/JSON.stringify json)])))

borkdude 2024-11-01T20:39:06.574519Z

can you share a playground link rather than pasting code?

borkdude 2024-11-01T20:39:17.177869Z

by hitting the share button

borkdude 2024-11-01T20:39:20.057749Z

and pasting it here

john 2024-11-01T20:45:21.681829Z

Well, that one has a typo json2

john 2024-11-01T20:45:31.258469Z

but I think it's throwing before it gets there

borkdude 2024-11-01T20:47:47.415709Z

doing an await as the last step in an async function won't help you, you need to move the deref to the call site

john 2024-11-01T20:48:06.392989Z

ah okay

john 2024-11-01T20:51:26.395199Z

I'd be curious what the performance trade off would be vs vanilla squint

borkdude 2024-11-01T20:52:09.315649Z

vanilla squint? none I think

borkdude 2024-11-01T20:52:31.929559Z

the generated code is the same as when you would've written js-await

m3tti 2024-11-02T07:04:03.361589Z

i have to say i like the idea but i have a kind of feeling that that might cause some sideeffects i'm not aware of. But it could also be that i'm just wrong. So in general this is a great move to have it that way.

john 2024-11-02T15:21:50.445889Z

Well, one other downside: if people ever ended up using formalisms in squint that act like cljs-thread (allowing the user to use real blocking semantics) then there might be some semantic divergence there from Clojure. In Clojure, a deref usually implies a blocking semantic, where that wouldn't be true here. And if, in the future, you wanted to accommodate those more clojury semantics in squint, it might be complicated having deref do all the right things for all three scenarios (regular deref, promise resolution and legit blocking)

john 2024-11-02T15:25:44.405979Z

It's only a matter of time before https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait becomes more mainstream, with objects/data that can live in a SAB. So a future where you might want to let deref handle wait may well happen

john 2024-11-02T15:28:42.090299Z

btw, I'm thinking that storing squint's compiler/environment state in a SAB could make multi-threaded squint even more clojury than cljs-thread. You wouldn't have to capture all bindings in user-land and ship them "over the wire" to the worker - context would follow execution from thread to thread transparently

john 2024-11-02T15:33:20.917409Z

In cljs-thread, I'm using a service-worker hack to enable blocking semantics, but .wait is the right way going forward. I'd imagine the security constraints around COOP/COEP for SABs will smooth out in the future

john 2024-11-02T15:36:42.772759Z

I've got an almost working impl of squint-thread (using Atomics.wait) - maybe your newer repl-bits will make that easier