squint 2024-06-03

Idea: support the optional chaining operator though some syntax

> x = {a: {b: {c: 1}}}
{ a: { b: { c: 1 } } }
> x?.a?.b?.c
1 
E.g.:
(def x {:a {:b {:c 1}}}) 
(.. x ?-a ?-b ?-c)
Right not this conflicts if there is actually a ?-a etc function. I've also considered supporting:
(-> x?.a?.b ...)
directly, but this also conflicts if there's already a x? variable somewhere. It would be nice to have a unambigious syntax for this, but perhaps it's not worth it. E.g. (..? x -a -b -c)

Don't want to stress out, take it as a FYI. There are also other peculiar operators. Like null ?? 'default' called nullish coalescing operator. And there is nullish coalescing assignment operator ??=

const a = { duration: 50 };

a.duration ??= 10;
console.log(a.duration);
// Expected output: 50

a.speed ??= 25;
console.log(a.speed);
// Expected output: 25
๐Ÿ™ˆ

The nullish one has support on the main branch

catjam Awesome

๐Ÿงต

how's this different than get-in early returning on nil?

it's compiled closer to what a JS engine supports, thus likely far better performance

maybe -?> or get-in-? or get?-in lol

get-in is dynamic, so that doesn't work, the point here is that you know the properties at compile time

i'm suggesting a new function/macro that creates the necessary syntax

.. seems far closer than -> though, since the arguments to -> are still dynamic

๐Ÿ‘ 1

maybe ..? which joins with ?. instead of .?

That is what I already suggested ;)

Here's a result from the squint REPL:

user=> (simple-benchmark [x {:a {:b {:c 1}}} f (js/eval "(x) => x?.a?.b?.c")] (f x) 100000000)
[x {:a {:b {:c 1}}} f (js/eval "(x) => x?.a?.b?.c")], (f x), 100000000 runs, 133 msecs
undefined
user=> (simple-benchmark [x {:a {:b {:c 1}}} f #(some-> % :a :b :c)] (f x) 100000000)
[x {:a {:b {:c 1}}} f (fn* [%1] (some-> %1 :a :b :c))], (f x), 100000000 runs, 1813 msecs

and what about allow for this case javascript syntax directly, example:

(def x {:x {:z {}})
(js/alert #js-expr x?.y?.z) -- undefined
how crazy is that?

Otherwise, this looks quite good: (..? x -a -b -c) as suggested above, but may lead to compatibility issue with cljs. Don't know how far you want to have parity.

btw how do you deal with the fact that properties in js can have spaces in them? In fact how the hell is cljs dealing with that? ๐Ÿ˜„

obj?.["evil trickster"]

๐Ÿ‘ป 1

I already explained the problem with the "directly" approach above

compatibility: squint already supports more than CLJS in some areas, so that's not an issue to me

spaces in properties: CLJS is dealing with that in the same way that JS is, you write a string

tl;dr: so far the ..? option looks best

Right. Also directly is kinda unsafe when it comes to code it can produce. So yeah. ..? looks the best

properties are munged as well, another source of breakage:

cljs.user=> (str (fn [x] x.dude?.quux))
"function (x){\nreturn x.dude_QMARK_.quux;\n}"

when you create JS objects like #js {:dude? 1}

yeah I noticed that at playground

cool, thanks for thinking along

I think ClojureDart has a kind of a.b.c syntax. But personally I think ..? or ?.. makes most sense in not being too jarring of a syntax compared to the rest. But -?prop would also be nice. And technically if that exists, people can build their own ..? or -?> macros no? So I like how that mimics Clojure's interop more. In that the real interop syntax is -prop and (. obj -prop)

What does the question mark mean here?

Aaaah, I was going to say, that looks super ugly IMO, but if it's already a thing in js yeah that might be the simplest all around, especially for js folks

I guess keys that have question marks at the end will just have two question marks? Any risk of clobbering?

Didibus his suggestion suffers from the same conflict issue, namely props kan start with question marks

Oh sorry, I misread

Your initial syntax doesn't have that problem though, right?

Yeah I think ..? Looks best.

I personally like the perc https://github.com/johnmn3/perc. More clojurey

But you can't do double colons in clj's reader, so it's not a perfect translation

So you'd do something like #.? :x:y

As a reader macro

I guess you wouldn't need double colons since js keys aren't ns qualified

too perly for my taste (even though perl may not have this, I think it looks too weird)

What about with spaces? Like a get-in or thread navigation, just starting with ..?

So no special syntax for the keys

Eh, maybe the dash is more understood for that interop use case

๐Ÿ‘ 1

What about ?-prop , that would actually be closer to the JS ?. operator. So ya it could be ?-?prop but that's true in JS as well a?.?prop no ? Or, instead of touching the prop, like there is . in Clojure for interop, there could also be ?. special operator. And again you could then implement whatever macro on top.

The downside is it's harder to combine. If you want to do: a.b?.c.d With the prop syntax you could do:

(.. a -b ?-c -d)
With a different operator you need to do:
(. (?. (. a -b) -c) -d)

Again, ?-prop isn't shielded from conflicts.

(.. x ?-prop) ;; => (.?-prop x)

You mean from existing code ? Wouldn't current code have has to be written as -?-prop

I'm thinking Clojurescript, because in Clojurescript you have to use dash for prop access no? Unlike in Clojure

let me write an example for you

every prop that doesn't start with a dash is handled as a function call in the .. macro

Right I see. I think I assumed that if squint added ?- syntax it would also special handle it where it needs too. Is it the .. macro that does that? I thought it was . itself that did that

you're right, it's .

squint could add special handling in .. for ? but this would break existing behavior

this is why I thought about making a ..? macro which is similar

So the issue with ?-prop is that it conflicts with functions that start with ?- if I understand correct? And so you can't tell if that's a function or a prop anymore?

And I feel like I'm looking at an anaphoric datalog query

And your ..? would be a special form and directly compile to JS?

..? would be a simlar macro as .. but just with the optional chaining

or maybe there should be a .? special form then as well (`.` is also a special form, not a macro)

I was thinking there needs to be a special form no? Otherwise how do you know to compile with ?. instead of . ?

You could also make it a special thread macro, so you can intersperse different logic between the fns in the chain. And then you can use it like a chain if you want.

But I guess we're going for a thing that compiles down optimally to an idiomatic js optional chain

@didibus you can hack it together without a special form using js* I believe, but it isn't pretty

Ah I see. Well I guess how it's implemented doesn't matter that much. I think ..? would probably be convenient. But like John was saying, I feel ideally (and maybe that's not doable), something that lets me mix and match between accessing Clojure values and JS props and lets me choose which access I'm the chain I want to be null-safe and which I want an error from would be the nicest to have.

Clojure values and JS props? in squint everything is JS

๐Ÿค” 1

So I was hoping you could do: (-> foo :bar (some-fn) ?-prop -prop) as a convoluted example ๐Ÿ˜…

calling it a night, thanks folks!

๐Ÿ‘ 1