Fork me on GitHub
#squint
<
2024-06-03
>
borkdude15:06:20

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)

Noah Bogart16:06:36

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

borkdude16:06:10

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

Noah Bogart16:06:24

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

borkdude16:06:45

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

Noah Bogart16:06:34

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

borkdude16:06:24

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

👍 1
Noah Bogart16:06:02

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

borkdude16:06:28

That is what I already suggested ;)

borkdude16:06:16

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

littleli16:06:43

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?

littleli16:06:19

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.

littleli17:06:23

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
borkdude17:06:25

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

borkdude17:06:49

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

borkdude17:06:16

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

borkdude17:06:29

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

littleli17:06:39

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

borkdude17:06:43

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

borkdude17:06:00

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

littleli17:06:09

yeah I noticed that at playground

borkdude17:06:32

cool, thanks for thinking along

didibus18:06:07

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)

john18:06:02

What does the question mark mean here?

john18:06:34

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

john18:06:47

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

borkdude18:06:33

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

john18:06:34

Oh sorry, I misread

john18:06:32

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

john19:06:12

Yeah I think ..? Looks best.

john19:06:09

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

john19:06:49

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

john19:06:13

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

john19:06:23

As a reader macro

john19:06:13

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

borkdude19:06:27

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

john19:06:44

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

john19:06:08

So no special syntax for the keys

john19:06:18

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

👍 1
didibus21:06:00

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.

didibus21:06:29

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)

borkdude21:06:54

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

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

didibus21:06:12

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

didibus21:06:47

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

borkdude21:06:50

let me write an example for you

borkdude22:06:57

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

didibus22:06:36

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

borkdude22:06:46

you're right, it's .

borkdude22:06:35

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

borkdude22:06:01

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

didibus22:06:28

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?

john22:06:22

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

didibus22:06:14

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

borkdude22:06:58

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

borkdude22:06:59

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

didibus22:06:50

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

john22:06:50

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.

john22:06:13

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

borkdude22:06:46

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

didibus22:06:22

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.

borkdude22:06:11

Clojure values and JS props? in squint everything is JS

1
didibus22:06:35

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

borkdude22:06:01

calling it a night, thanks folks!

👍 1