clojure-dev 2023-02-13

ClojureDart has a feature to destructure fields in keys destructuring using {:flds [a b c]} . In squint-cljs I've supported this as ^:js {:keys [a b c]} as to not have to make changes to clj-kondo. I guess Cursive would also have to make a change to support :flds - am I right @cfleming? I wonder if there's support for adopting this more broadly (in CLJS: @dnolen, in CLJ: @alexmiller). Field destructuring doesn't sound unique to ClojureDart and it would be nice not to have to make bespoke (albeit small) changes to static analyzers for Clojure dialects. cc @cgrand @baptiste-from-paris ❤️

still about :flds: Java 19 has a "record", that is a immutable class. https://docs.oracle.com/en/java/javase/19/language/records.html Its getters are not getField, but just field I think that :flds [foo] => .getFoo is not future-proof.

Alex Miller (Clojure team) 2023-02-14T16:16:13.281589Z

also many Java classes have .get methods with side effects - seems like that would be bad with destructuring

btw, bean an URL makes side-effects (and throws if you are offline)

In Java :flds doesn't seem to be make much sense, maybe I should have started this conversation in #cljs-dev

Alex Miller (Clojure team) 2023-02-14T16:18:46.315189Z

field destructuring seems potentially interesting, but I'm not sure how much this would be needed in Java interop, probably more likely in cljs?

Alex Miller (Clojure team) 2023-02-14T16:18:53.239989Z

jinx

🙌 1
Alex Miller (Clojure team) 2023-02-14T16:19:31.940099Z

also, why not :fields ? I know the others are xyzs but they had useful first syllables/prefixes

baptiste-from-paris 2023-02-14T16:20:25.602279Z

@cgrand was really attached to a 4 letters word...

Alex Miller (Clojure team) 2023-02-14T16:20:53.008859Z

we use the more generic term "member" a lot too, did you consider :mems ?

baptiste-from-paris 2023-02-14T16:22:25.462239Z

no, I don't recall considering :mems

or just

{:- [a b c]}
:P

mems make me thinks of memories (or memo*) more than members

in CLJS I find this pretty unattractive because of the advanced compilation problem

it’s yet another thing to explain

and there’s already a lot to explain

the alternative isn't much easier to explain imho

Iirc the CLR has getters/setters too so @dmiller might be interested in supporting something like that

Alex Miller (Clojure team) 2023-02-14T17:03:38.825739Z

well as above, getters seem problematic (vs fields)

JavaScript fields can also cause side effects (there are getters and setters, and you can implement your own trickery with Proxy)

but if you wanted to call those fields/methods anyway, what's the problem in destructuring vs manually calling them?

btw I'm not advocating for this feature, but just wanted to clear up what problem you see in this :)

💯 1
Alex Miller (Clojure team) 2023-02-14T17:15:18.675469Z

it may force some semantic ordering of actions that is not currently specified in merely accessing data

Isn’t this tied to what’s idiomatic in a given ecosystem/community? In Java there are side-effecting getters in the “core” classes. Dart/Flutter seem more well behaved towards what getters do. In Clojure a deviant ILookup impl can break havoc. Well bean as mentioned above.

Alex Miller (Clojure team) 2023-02-14T17:46:17.060829Z

these are just things to think about

@cgrand Very much interested in this problem from the viewpoint of ClojureCLR. It's been a problem.

Yes, Cursive would have to make a change for this.

for CLJS it would make sense to have a :js/keys and :js/props to be able to easily access thing.x and thing["x"], since it makes a difference for the closure compiler which style you use

the first gets renamed/optimized, the second not?

might get renamed yes, depends on externs. second is never modified

could use :js/flds to keep the 4 char theme 😉

would be nice to have a unified way of doing this across CLJ dialect, which is kind of the point of the original post: not having to support dialect-specific stuff too much :)

e.g. if the first is the most common, that could be the :flds way

But any sort of analysis will need to know what the fields are coming from though, right? e.g. Java object, JS object, Dart object. Even if the surface syntax is the same.

> Field destructuring doesn’t sound unique It’s more important in Dart because, fields have getter/setters. On the JVM it wouldn’t make sense as public fields are rare. Except if you are ready for :flds [foo] to expand to .getFoo.

"host object" is the common theme here so I think the (static) analysis should (roughly?) be the same for all?

well, clojure itself uses public fields, but I agree it's not that idiomatic ;)

in JS it's way more common

> Except if you are ready for :flds [foo] to expand to .getFoo. Having used Kotlin, I would actually like this, as well as (.foo x) (or perhaps (.-foo x))

Note that (.foo x) will actually first try the method foo and then the field foo, if the method doesn't exist, in Clojure

so :flds [getFoo] might work, if it expands to (.getFoo x)

More generally what about static analyzer positing that {:unknown-kw [names ...]} in destrucring is going to bind the provided names.

Yeah, but the .- might make it clearer that you’re expecting to access it like a property.

> Having used Kotlin, I would actually like this, as well as (.foo x) (or perhaps (.-foo x)) But a class may have both a method named foo and one named .getFoo — unlikely but I’m sure there are specimens out there. How does kotlin deal with the ambiguity?

> Yeah, but the .- might make it clearer that you’re expecting Didn't understand this. The expansion isn't visible to the user so it doesn't matter what you expand to, as long as it works for both fields and methods, which JVM Clojure already supports (if you don't automatically prepend get, just use the raw field/method name)

While we are on sugarized form: we also have (.-prop! x v) as a shorthand for (set! (.-prop x) v) because 1/ setting fields is common beacuse of setters 2/ it works better with doto

🤔 1

> How does kotlin deal with the ambiguity? I’m not sure, those classes are sufficiently rare that I’ve not seen one. But note that the method would be invoked as x.foo() whereas the property would be either a = x.foo or x.foo = b (desugaring to .getFoo() and .setFoo() respectively), so even for offending classes perhaps the syntax usually disambiguates - I’m not sure of all the details.

without that what you can do on the JVM with (doto (Obj.) (.setFoo x) (.setBar y)) becomes (doto (Obj.) (-> .-foo (set! x)) (-> .-bar (set! y)). That’s why Cljd has (doto (Obj) (.-foo! x) (.-bar! y))

Which is ugly - I had imagined it more for read access.

@cfleming ah yes missing parens help disambiguating it, so it would be a dash, still room for conflict but more unlikely (public field vs setField)

It’s not something I had thought about a lot, just the sort of thing that occurred to me in the shower.

I like your (.-prop! x v) shorthand, I’ve never liked the set! syntax much.

I think in CLJS this is valid:

(set! x -prop v)
I don't have a strong preference, but again I would have liked it if ClojureDart would have adopted that (maybe it does?) since tooling already supports this and -prop! looks like a regular field access, now you have to analyze the ending of the name for an exclamation mark, etc

(set! x -prop v) 🤔 I though we had implemented something similar, but it was apprently ditched 😞 We can support it too. As for .xyz if you want to do anything useful with it you need to have dialect specific member resolution anyway.

if you do some kind of type inference, yes, but clj-kondo currently does't choke on it. it doesn't even check that it's not valid to have the last "arguments" here (.-foo x 1 2 3) , but one day it might :)

anyway, thanks for the heads up ...

so the ! is not an issue then?

currently not

but it might be one day, I guess we'll find out :)

not sure about Cursive here

Well we have a couple of other stuff like dotless constructors, method names which are invalid symbols etc.

Named arguments.

@cgrand Is there a reference with these changes compared to other impls?

I would also be very interested in that.

Great, thank you - I have been meaning to sit down and make a proper effort at properly supporting ClojureDart, this will help.

❤️ 4

🤔 considering switching from :flds to .flds to clearly mark it as an interop feature

👎 1