Fork me on GitHub
#clojurescript
<
2018-04-26
>
alex-dixon00:04:00

I think I can make due with that. It seems like a nice pattern emerges with data structured as :company/name instead of {:company {:name …}}, --- things get flatter. If I have a lot of keys that I’m destructuring though and one is off then it seems like we aren’t able to let that be the exception and state it twice and everything else once. We have to state everything twice. 😕

jimberlage00:04:21

You’re actually not committed to just that method of destructuring, if it helps - only the stuff that needs a different name

jimberlage00:04:35

cljs.user=> (let [user {:user/keys {:id 56 :name "Jim Berlage" :cl "js"}}
       #_=>       company {:company/keys {:id 28 :name "Guaranteed Rate" :business "financial"}}
       #_=>       {{user-id :id :keys [name cl]} :user/keys} user
       #_=>       {{id :id company-name :name :keys [business]} :company/keys} company]
       #_=>   [user-id name cl id company-name business])
[56 "Jim Berlage" "js" 28 "Guaranteed Rate" "financial"]

🎉 4
jimberlage00:04:42

You can still use :keys, :as, etc., and only specify that some variables have different names

alex-dixon14:04:30

Thanks…this is more than I knew was possible. Still, it seems it requires structuring a map? I’m getting this for user on line 1: #:user{:keys {:id 56, :name “Jim Berlage”, :cl “js”}}. My data would be more like {:company/name “foo” :user/name “bar}, so I’d be trying to destructure a single map with keys prefixed with e.g. “user/” and “company/” and rename the ones that clash between those namespaces. Maybe I just shouldn’t model my data that way

Hendrik Poernama09:04:13

(fn [obj] (let [v1 (goog.object/get obj "k1") v2 (goog.object/get v1 "k2") v3 (goog.object/get v1 "k3")] ... ))

Hendrik Poernama09:04:03

is there a better way to do this? when obj is #js {} it crashes because goog.object/get will return undefined but won't take undefined

Hendrik Poernama09:04:20

right now my solution is to always use nil as default value, but something tells me I would forget and crash.. Not to mention very verbose

Hendrik Poernama09:04:28

anyone thought about destructuring macro for js objects yet?

Hendrik Poernama11:04:44

Apparently getValueByKeys does not take nil as input while goog.object/get does. Very tricky!

pesterhazy11:04:37

Try (some-> v (goog.object/getValueByKeys #js["a" "b"]))

Hendrik Poernama11:04:07

nice, and that actually covers nil and undefined

pesterhazy09:04:59

I've thought about a destructuring macro, yes, and I think it would be a good idea in principle (perhaps a js-obj aware version of let)

thheller09:04:51

well you could always use (.-k1 obj) and so on. all it takes is one ^js obj hint.

Hendrik Poernama11:04:05

this works, but the tradeoff is verbosity when the key is optional. (.. #js {:a 1} -b -b) throws for example, instead of returning nil. We then have to walk the structure one step at a time and nil/undef check on every step.

Hendrik Poernama11:04:09

which is quite normal in js world, but cljs has get-in 🙂

pesterhazy10:04:11

is that an extension in shadow-cljs or available in the cljs compiler by default?

thheller10:04:14

should work in CLJS but you may need to turn on some :infer-externs option first

thheller10:04:40

it might complain if you only use ^js and not ^js/SomeClass. not sure actually.

mfikes11:04:56

Another alternative is to extend object to ILookup and then you can do

(get-in obj [:k1 :k2 :k3])

Hendrik Poernama11:04:33

@mfikes interesing, wonder if there's any side effect?

pesterhazy11:04:21

Personally extending to ILookup feels risky

Hendrik Poernama11:04:47

actually, it may not work when the js object is deliberately created without inheriting js/Object

mfikes11:04:14

@pesterhazy I agree. I've been trying it for a while to see if anything breaks.

mfikes11:04:53

@poernahi The make effect is to make get work in the cond branch that checks ILookup. But I'd be curious if anything else goes wrong.

pesterhazy11:04:56

If we could get ^js obj type hints to work generally, like @thheller suggested, that would be a big step forward

thheller11:04:38

FWIW your nested thing fails in shadow-cljs too

thheller11:04:51

seems like some-> doesn't propagate the hints properly

pesterhazy11:04:03

that's a pity

thheller12:04:28

it works perfectly fine in shadow-cljs actually

thheller12:04:53

BUT long gets munged by the CLJS compiler to long$

thheller12:04:14

thats actually a deal breaker for using . for this 😞

thheller12:04:04

(defn demo [obj]
  (prn [(some-> obj (.-very) (.-foo))
        (goog.object/getValueByKeys obj "very" "foo")]))

(defn demo2 [^js obj]
  (prn [(some-> obj (.-very) (.-foo))
        (goog.object/getValueByKeys obj "very" "foo")]))

(defn demo3 [^js/Object obj]
  (prn [(some-> obj (.-very) (.-foo))
        (goog.object/getValueByKeys obj "very" "foo")]))

(let [obj #js {"very" #js {"foo" "ok"}}]
  (prn obj)
  (println "demo")
  (demo obj)
  (println "demo2")
  (demo2 obj)
  (println "demo3")
  (demo3 obj))

thheller12:04:06

this works fine

pesterhazy12:04:49

did a s/long/lengthy/ in my repo to rule out this issue

Hendrik Poernama12:04:00

this also works with prototype-less obj (.create js/Object nil #js {:very #js {:value #js {:lengthy "ok"}}})

thheller12:04:59

fun fact: since the demo2 and demo3 generate the externs demo will also work. although externs inference still complains since it comes first.

pesterhazy12:04:21

so much magic around externs. Some property names are blacklisted so they don't trigger minification. Externs for one lib can influence minification for another part of your program...

pesterhazy12:04:19

initially I treid #js{"alpha" #js{"beta" "ok"}}- that didn't minify for some reason

thheller12:04:36

yeah externs only really work in fully typed programs. but CLJS isn't so any property defined in externs won't be renamed ever. thats why externs generators are so bad.

thheller12:04:14

well .. you are probably not loosing too much by having too many externs but still

pesterhazy12:04:58

too many externs is totally fine by me

pesterhazy12:04:44

I worry about things randomly failing because I don't understand the logic behind when you need to annotate

thheller12:04:34

yeah agreed. making it work is more important than making it small. that comes later 🙂

pesterhazy11:04:56

When I try :infer-externs true with cljs.main, that leads to a NPE: https://github.com/pesterhazy/see-el-jay-ess/blob/master/README.md#infering-externs

pesterhazy11:04:10

@thheller I've updated my test repo to include a non-nested case in addition to the nested map

pesterhazy11:04:55

With infer-externs false property access returns nil; with infer-externs true it crashes.

mfikes11:04:50

@pesterhazy Even though you don't have a minimal repro, if your NPE stack is safe to put in a gist I'd be happy to take a quick look at it.

mfikes12:04:23

Oh the NPE is in your repo?

pesterhazy12:04:33

just clone and run that script

pesterhazy12:04:47

scripts/run-test-deps --infer

mfikes12:04:31

Cool. I can repro.

mfikes12:04:53

Crap looks like it is Closure

pesterhazy12:04:16

@mfikes, I wonder if it works for others, and if so what's special about my repro-repo

pesterhazy12:04:29

just the fact that I'm using cljs.main?

mfikes12:04:42

@pesterhazy If you revise your deps.edn to point at ClojureScript master, it no longer crashes, and the printed output seems to imply it it is working (I haven't read what that repo's code is doing). Specifically this coordinate seems to be OK

{:git/url "" :sha "132d3aa232921a3cea66f830d61c89be78c581cb"}

pesterhazy12:04:46

@mfikes I can confirm that it doesn't crash anymore, thanks for that!

mfikes12:04:54

I'm trying to bisect to see what ClojureScript change caused it to work.

pesterhazy12:04:55

two observations: - with infer-externs the type hints don't seem to matter, given that demo, demo2 and demo3 print the same output - nested key access using (some-> obj (.-very) (.-lengthy)) doesn't seem to work

pesterhazy12:04:20

that looks very plausible

mfikes12:04:25

By the way, as an aside, setting a :local/root to ClojureScript lets you do a git bisect directly on that repo to find what fixed or broke a project like Paulus's which uses deps.edn. TL;DR deps.edn made bisecting this trivial. 🙂

pesterhazy13:04:09

Isn't it great when a simplification (like using clj instead of lein) suddenly opens up unexpected possibilities?

pesterhazy13:04:26

(I didn't know, and if anyone else is wondering, :local/root is explained here: https://clojure.org/guides/deps_and_cli#_using_local_libraries)

pesterhazy13:04:28

@mfikes out of curiosity, do you manually accept or reject a step when using git bisect, or do you rely on the exit status of a script?

mfikes13:04:49

@pesterhazy Manually. Especially since I was running your repro to see if it worked or failed each time. It was only about 5 steps IIRC.

alex-dixon14:04:30

Thanks…this is more than I knew was possible. Still, it seems it requires structuring a map? I’m getting this for user on line 1: #:user{:keys {:id 56, :name “Jim Berlage”, :cl “js”}}. My data would be more like {:company/name “foo” :user/name “bar}, so I’d be trying to destructure a single map with keys prefixed with e.g. “user/” and “company/” and rename the ones that clash between those namespaces. Maybe I just shouldn’t model my data that way

Alex Miller (Clojure team)14:04:44

you can destructure with something like {cname :company/name, uname :user/name, :company/keys [address], :user/keys [birthday id]} etc

Alex Miller (Clojure team)14:04:02

so using a mixture of keys destructuring and renaming

🎉 4
alex-dixon14:04:23

Thank you so much!! Exactly what I was hoping for:

(def m {:course/title           "course title"
        :lesson/title           "lesson title"
        :back-to-course         "back to course"
        :lesson/intro-paragraph "intro paragraph"
        :course/header-image    "header image"
        :course/tags            "tags"})
(let [{course-title :course/title
       lesson-title :lesson/title
       :course/keys [header-image tags]
       :lesson/keys [intro-paragraph]
       :keys        [back-to-course]} m] 
  [course-title lesson-title header-image tags intro-paragraph back-to-course])
=> ["coure title" "lesson title" "header image" "tags" "intro paragraph" "back to course"]
Beautiful. Thank you

alex-dixon14:04:48

If so and help is still wanted I can try

alex-dixon14:04:33

“this” == destructuring namespaced kw maps when some unnamespaced versions clash, combining :keys, :keys/foo and {sym :ns/kw} for brevity. I don’t see an example on the site that shows how they can be combined. Would you want one? I looked at the docs yesterday and wasn’t able to figure it out myself

Alex Miller (Clojure team)14:04:44

certainly would be good in the guide

Alex Miller (Clojure team)14:04:17

in reference page, my goals are a little different there

alex-dixon15:04:34

Ok. Will focus on the guide instead of the reference. What’s the difference between the two? Why should the reference not show those examples? Not disagreeing just trying to understand

Alex Miller (Clojure team)15:04:37

the reference is … reference - it attempts to define all of the functionality and the way it can be composed. whereas the guide is “how do I use it” and may not necessarily be exhaustive.

wombawomba14:04:32

I’m trying to add an npm dependency (`react-ace`) to my project (via :npm-deps), but I’m getting an error:

index.js:20 Uncaught ReferenceError: module is not defined
    at index.js:20
The error is in the following code:
var freeModule$$module$project_path$node_modules$lodash_isequal$index=freeExports$$module$project_path$node_modules$lodash_isequal$index&&"object"=="object"&&module&&!module.nodeType&&module
AFAICT that line is related to lodash.isequal, which react-ace depends on. Any idea what’s happening here?

alex-dixon14:04:23

Thank you so much!! Exactly what I was hoping for:

(def m {:course/title           "course title"
        :lesson/title           "lesson title"
        :back-to-course         "back to course"
        :lesson/intro-paragraph "intro paragraph"
        :course/header-image    "header image"
        :course/tags            "tags"})
(let [{course-title :course/title
       lesson-title :lesson/title
       :course/keys [header-image tags]
       :lesson/keys [intro-paragraph]
       :keys        [back-to-course]} m] 
  [course-title lesson-title header-image tags intro-paragraph back-to-course])
=> ["coure title" "lesson title" "header image" "tags" "intro paragraph" "back to course"]
Beautiful. Thank you

Alex Miller (Clojure team)14:04:22

you’re missing a ] in calcs

eoliphant14:04:47

ah yeah I just typed that here, it’s right in the code 🙂

eoliphant14:04:38

I’m in macro hell lol

borkdude14:04:35

@alex-dixon huh, this works?

(let [{:course/keys [header-image tags]} {:course/header-image "foo" :course/tags "bar"}] [header-image tags])
Why didn’t anyone tell me?! Where is this documented? 🙂

💯 4
😀 4
alex-dixon15:04:02

Awesome right?

borkdude15:04:18

yes, life changing

alex-dixon15:04:44

Was going to say the same thing lol. refactors whole app

wombawomba14:04:02

Is it still the case that CommonJS modules won’t work properly with cljs?

Alex Miller (Clojure team)15:04:00

to be specific, the :course/keys stuff is new in 1.9. everything else is old.

Alex Miller (Clojure team)14:04:07

but there is a ticket to improve that and extend the destructuring guide

mfikes14:04:09

Whoah! Super cool. TIL. That needs to be promulgated 🙂

wombawomba14:04:24

I’m trying to google for the module is not defined error I pasted above, and this is all I can find — trying to figure out if it’s out of date or not: https://stackoverflow.com/questions/35489797/using-react-components-in-reagent

pesterhazy15:04:23

@wombawomba not sure about npm-deps, but you can make it work pretty easily using the "double bundle" method: https://github.com/pesterhazy/presumably/blob/master/posts/double-bundle.md and https://github.com/pesterhazy/double-bundle

pesterhazy15:04:19

that's still my personal go-to method until all the cljs-node-module-require kinks are ironed out

borkdude15:04:44

until now I didn’t realize you can mix keys with the other notation. I guess it’s just the unqualified version of the new feature:

(let [{a :a :keys [b]} {:a 1 :b 2}] [a b])
it makes so much sense, how could I not see it 😉

borkdude15:04:27

I’ve been using mostly :keys so far

borkdude15:04:12

multiple also supported:

(let [{:myns/keys [a] :keys [b] :myotherns/keys [c]} {:myns/a 1 :b 2 :myotherns/c 3}] [a b c])

souenzzo23:04:43

:keys[ns1/v1 ns2/v2] also possible. You can also use :syms it's a map from symbol to values and :strs to strings.

Alex Miller (Clojure team)15:04:32

yeah, none of that is new other than :myns/keys

borkdude15:04:29

yes, I thought they were exclusive and I managed to hold up that believe for years 😉

Alex Miller (Clojure team)15:04:37

well, lots of people combine :keys and :as at the same time

Alex Miller (Clojure team)15:04:03

but other than that, most people don’t do multiple kinds of associative destructuring in a single case

borkdude15:04:10

yes, I did that, but I mean the combination of {a :a} and :keys

Alex Miller (Clojure team)15:04:27

ah, yeah, nothing new there

borkdude15:04:47

I’m just catching up with 1.0…

Alex Miller (Clojure team)15:04:24

increasing use of namespaced keys has put a lot of pressure on associative destructuring over the last few years

👍 4
wombawomba15:04:23

@pesterhazy cool, I’ll try that approach! thanks

alex-dixon17:04:29

Is there a function that creates a namespaced map or is it just the reader macro?

mfikes17:04:47

@alex-dixon The concept of “namespaced map” doesn’t really exist. The keys in a map can be namespaced.

👍 4
Alex Miller (Clojure team)18:04:30

in other words, this is a syntax - the objects are identical