This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2019-05-08
Channels
- # announcements (12)
- # aws (4)
- # beginners (92)
- # calva (2)
- # cider (28)
- # clara (11)
- # clj-kondo (5)
- # cljdoc (9)
- # cljs-dev (195)
- # cljsjs (1)
- # clojure (82)
- # clojure-italy (18)
- # clojure-losangeles (1)
- # clojure-nl (2)
- # clojure-spec (10)
- # clojure-uk (51)
- # clojurescript (40)
- # cursive (7)
- # datomic (19)
- # duct (9)
- # emacs (2)
- # figwheel (3)
- # fulcro (56)
- # graphql (8)
- # jobs (3)
- # luminus (2)
- # off-topic (15)
- # re-frame (5)
- # reitit (3)
- # ring (5)
- # shadow-cljs (22)
- # slack-help (2)
- # sql (51)
- # test-check (9)
- # vim (64)
- # yada (1)
a little exploration of possible ways to support destructuring of js keys: https://dev.maria.cloud/gist/691ace12d14523337c7658ebb209c064?eval=true
@mhuebert reminded me of a similar approach I was looking into
(require '[goog.object :as obj])
(let [{:goog.object/keys [x y]} #js {:x 1 :y 2}
{::obj/keys [z]} #js {:z 3}]
...)
Yeah, I was thinking along those lines with the :js-keys
direction. The downside I see there is that unlike :strs
, :syms
and :keys
there is no obvious way one could rename the binding. (Aside from the renamable keys issue, but that’s really the limitation of goog.object/get)
:js/keys
also looks nice
Clojure has no notation to access raw objects in java It will bring complexities/ambiguities to clj(c) ecosystem
Yes, the .-key
forms would need to be within reader conditional :cljs
branches in cljc files. Incorrect usage would throw at compile-time, so you wouldn’t have runtime errors.
Property access is a pretty low-level host concern and there are already lots of gotcha’s/differences between cljs and clj there.
user=> (bean (java.util.Date.))
{:day 3, :date 8, :time 1557321124396, :month 4, :seconds 4, :year 119, :class java.util.Date, :timezoneOffset -60, :hours 14, :minutes 12}
there's also https://github.com/clojure/java.data for a different take on same idea
but that's a data transformation approach, not access
Yes, the .-key
forms would need to be within reader conditional :cljs
branches in cljc files. Incorrect usage would throw at compile-time, so you wouldn’t have runtime errors.
.- is valid in clj too (for fields)
Yes - one difference is that in clojure, unlike cljs, we can’t detect presence of a .-key
at runtime (AFAIK), and destructuring forms assume support for this lookup/not-found semantic
I mean you could support {x .-field}
destructuring in clojure, but it would necessarily behave differently than all other destructuring lookups, whereas in JS we can actually get the expected lookup semantic
yeah, no interest in doing that in Clojure
b/c most fields are private in typical Java classes, this would rarely be useful
I came to the same conclusion after asking around recently to see if it would make any sense at all for js-interop forms to compile to something sensible in Clojure (to work in cljc). Did not seem like a useful direction, though some folks have asked for it.
@mhuebert it's worth noting this has been suggested before, and I even toyed around with it myself in the old days (2012?), I even had a discussion with Rich about it - and I think in the end I agreed [with his conclusion that it's not a good idea]
as you say #2 and #3 are just non-starters. The problem with #1 is it's just "ugly" since it's really a special case
also years of experience on projects and viewing other people's project has shown it's just not a big deal (certainly not coming up as a big ticket item on surveys) - so we put in something that doesn't really fit that only has minimal impact
I didn’t really see this as a tractable problem until I got a handle on how to work with renamable vs static keys cleanly (eg checking for presence of a renamable key)
Personally i would use it a lot, but i think it depends how much host-interop one does and how often one is in a perf-sensitive context
and thus far I don't see enough value - lots of folks are writing apps with Reagent, using Transit for interchange, with no interop to be seen
it would be useful to know about concrete APIs where you and others feel like this is paying its way
One confounding variable is that the more inconvenient host-interop syntax is, the more likely one is to avoid raw interop and prefer wrappers or cljs-specific stuff
I've found that situation between Clojure & ClojureScript here to be hardly any different
B) interop stuff can be pushed to edges for the few things that you need, and those can be behind some simple fns calls to hide the details
in my own conversations with other cljs devs, destructuring comes up a lot as a pain point
it’s also a frequently commented-on and appreciated feature of js-interop (has a little lookup
helper)
my experience is with answering questions in #reagent / #shadow-cljs, and a lot of them have to do with people wanting to use JS components and doing JS interop. what I’ve seen is: 1. most people in the community go to great lengths to avoid doing “gross” JS interop-y stuff (at least at the syntax level) 2. this friction dissuades people from either adopting CLJS (because they’re coming from JS) or from using 3rd party JS libs, which is a net negative in terms of productivity in a lot of cases
I think (1) is more than education, it’s more about “I want to do Clojure(-y) things and this interop stuff doesn’t feel or look like Clojure”
i think the parts of host-interop that work well, are part of what make clojure(script) attractive, so I don’t see why we would not want host-interop to be as clean as possible. for every 10 (or 100) crap JS libs there is 1 really good one that is nice to be able to use without a lot of wrapping effort
but in any case I am not in a rush and didn’t make this little exploration as a crusade 🙂.
hrm, I don't have much more to say here because we keep packing in too many assumptions in our statements (and I'm not helping clearly)
I guess this hits close to home for me because the React wrapper I’ve been working on basically is just there for destructuring props + parsing hiccup. a lot of the positive feedback has come from how easy it is to use and build regular React components, with close to the same ergonomics as reagent
@mhuebert can your proposal work as a lib or does it require a change to core destructuring?
but if it doesn’t speak to you, I understand. I’m not going to get bent out of shape about it
@lilactown how is this relevant to your wrapper?
@john not sure if you saw the doc - https://dev.maria.cloud/gist/691ace12d14523337c7658ebb209c064?eval=true - it could be used as a lib, if one was willing to use non-core variants of whatever macro is wrapping destructure
, eg fn
defn
let
ah, not sure I understand the question. I’m not trying to prescribe someone else do my work, just saying that I think I have experienced positive impact and feedback with this kind of thing
Well, you could provide your own destructure macro and use it in place. Would that lose some performance benefits?
one issue is that you can’t use a destructuring macro in-place, you have to rewrite the containing macro. eg (let (destructure [my thing])) isn’t valid
I guess I'm thinking of something more akin to your lookup thing, so perhaps I'm not fully understanding. I gotta give your gist a more thorough read later today.
Seems like js-interop is a great place to incubate ideas and actually test the appetite of the community for these different kinds of ideas.
there's nothing wrong with making beautiful wrapper that's going to sit at the core of a lot of projects
yeah, I expect I’d find more things to build into a wrapper. was just trying to relay my experience with providing this sort of destructuring API for JS objs
the point for me was never to "wrap" React - rather set it up so you could drive it with persistent data structures
most rando-libs don't work that way - there's no benefit to permit a persistent data structures API and a huge perf hole
so I understand even less the desire to wrap - since there's no win - other than some superficially nice looking API
I want to remove the need for wrappers personally. I think making the syntax nicer would help remove some of the impetus for them.
I don't really understand - destructuring doesn't have anything do w/ wrappers or no wrappers
anyways, gotta run - but I really think it's worth reviewing Rich Hickey's thoughts on interop a bit more closely
it does, when the impetus for superficial wrappers is that sometimes working with js data structures is simply annoying. what @lilactown said earlier, that a lot of his code is written just to make it easier to destructure JS objects, and that his users appreciate that.
if we rephrase “superficially nice” as “elegant and pleasing to use” we get much of the appeal of clojure proper. if you can work elegantly with host data structures with no need for wrapping, then why wrap? somehow we all agree that wrapping isn’t idiomatic clojure, we like elegant host interop, i guess it is a question of the cost of augmenting destructure and how far one wants to go
@thheller before I forget, re: the let
thing - before you get too far let's make sure it's behind a flag (which may become a default) - completely dropping how we currently do things for var
should be separate consideration step and probably much later down the line
@mhuebert all the other stuff aside, it's what I don't like about #1 - from a Clojure language design standpoint it doesn't really jive with what's there - Rich & I went around on exactly this point
I think part of the disagreement here js conflates "struct" and "dict" uses of objects
clojure absolutely supports that interface almost as well and natively as its own ilookup and nth
in clojurescript/js, the situation is more confusing because of js's lack of distinction
@dnolen fair enough, I see how the js side would be different from how all the other destructuring lookups work, and that it might not be desirable to have this in the language.
google closure compiler makes this a bit worse because advanced compilation needs a syntax-level distinction
even JS has issues with destructuring it’s own data because of it’s lack of distinction
let { someMethod } = someObj;
someMethod(); // => Error! `this` is undefined
one could imagine that if there was more of a distinction between a dict (or something with “lookupable” properties?) and an object that destructuring someMethod
wouldn’t even be possible ¯\(ツ)/¯
when they conceived of their object destructuring syntax they still did want it for destructuring arbitrary objects
yeah, and it does allow you to do funky things like:
let { someMethod } = objA;
someMethod.call(objB); // `this` is objB now
AFAICT Map
would be much easier to optimize, and 80% of the time it’s what people actually want
this is also the biggest pain for google clojure adv compilation too, because it needs struct and dict uses to be separate and use separate syntax
struct uses need unquoted keys (not string access syntax) so it can rename; dict uses should always quote so it won't rename (it can cross a library or runtime boundary safely)
@mhuebert perhaps another way - I think records in Clojure have an optimization that allow field lookup (at call site)?
perhaps worth looking at how that works and that will lead to a better idea that requires no changes - and faster records
if we do something for records and it's not a code size issue, maybe JS objects can piggieback
> there’s no protocol for this thing you want to do - so it’s for a concrete case > destructuring right now is behind protocols so anyone can participate to be clear the problem here is that it introduces a kind of impurity to the destructuring process - that it is no longer entirely protocol-driven - not that the property-access interferes with any existing lookups
and what you're proposing is something that isn't destructuring in any previous sense of the term
@favila certainly can do that - do you mean something different from what I did in the gist?
i’m not sure how one could do something in destructure
for JS objects that doesn’t involve adding specific, intentional syntax.. also because you need to be able to differentiate renamable vs static keys
ok, I wasn't thinking you could do both, but if your syntax is distinct enough you can
I didn't read it carefully but I'm not sure how you get away with calling (destructure) first instead of last
yeah. it leverages the fact that .-dot-keys
are both associated with host-interop, and not valid except in special circumstances, so i can use them here without any chance of breaking existing things
seems like you would need to pick out your syntax first and then feed the rest to destructure
letting destructure
do its work first keeps it simple, and is particularly clean because i have a j/get
with the same api as cljs.core/get
, so i can swap them out at the end when recognizing a js lookup
js-interop/get supports keywords for static keys, ie string syntax, o["lookup"]
, or dot-keys for o.lookup
using a .-dot-symbol
emits o.lookup
, keyword/string emits o["lookup"]
. this js-let would emit o.lookup
since it is using the .-dot-keys
, these would be compiled to o["lookup"]
if you had a ^js
hint on the target object or if the property was in your externs
anyway, this seems neat, I'm not sure of the value of mixing the two (i.e. js-let supporting normal let and also special js property destructuring) but it's something I might pull from a library sometimes
That might depend on your tools. After I added support for renamable keys to js-interop, I’ve started using them more often and quite liking them. But in raw cljs can be pretty awkward just to create an object with a renamable key. Like
(let [o #js {}]
(set! (.-theKey o) 10)
o)
but I would think you might want to use renamable keys for the same reasons that all of CLJS does