Fork me on GitHub
#clojurescript
<
2020-05-17
>
Oliver George12:05:36

Anyone familiar with the new CLJS webpack bundling and exports? I'm trying to get a reagent component to render via storybook - it works with some caveats. I'd like to better understand how it should work and what the limitations are.

Oliver George12:05:03

Questions... 1. in stories/2-SimpleComponent.stories.js is it sensible to import '../out/index.js'; ? 2. it doesn't work for -O none until i setup out to be staticly served. Is there an alternative where the webpack bundler handles that. I'm imagining some additional imports before my core namespace. (replacing the network requests). The other issue here is the delay loading the namespace causes my code a race condition. 3. is there a more correct way to access my code via a javascript style "export default ..." type statement in my code? Currently I'm accessing via a ^:export

Aron14:05:49

so, I have a question everyone will love. I googled around before asking and found this: https://github.com/athomasoriginal/clojurescript-30/tree/master/07-array-cardio-2 And I saw it elsewhere too, that the standard answer is to convert the data to hash-map, which is find if you want to read the value. But what if I have to update it? Should automatically change everything into a custom datastructure setup for the sake of accomodating the language?

Aron14:05:55

Because this is basically about what I send to back to the server, if there is an "id" key that's not nil, then I have to http PUT instead of http POST, and change (or replace, performance is not an issue here)

orestis14:05:20

I’m not sure what the question is? Transforming native JS values to Clojure data structures?

dpsutton14:05:16

agreed. I see a "standard answer" but have no idea what the question is nor what the link is supposed to demonstrate

Aron15:05:05

if you have [A1, A2, A3, .... , An] where A is a large object each with an id property, I give you id and a list of keys for something like assoc-in or update-in, and a value to update the map with the id at that key (branch?), what do you use? Apart from reduce, because with reduce everything is possible, I know it it's like using a while or a for loop

aisamu15:05:46

There's now https://github.com/mfikes/cljs-bean, if you're talking about the interop

lilactown15:05:50

yeah probably reduce

lilactown15:05:58

it doesn’t sound like this is about interop

8
aisamu15:05:17

> convert the data to hash-map, which is find if you want to read the value. But what if I have to update it? Yup, I guess I assumed that non-hashmaps would be JS objects in a JS array

lilactown15:05:35

@ashnur i’m not sure what you have against reduce; it is perfect for these sort of corner cases / bulk operations. what you basically want is to find, in an unordered list, the map with a specific id and then update that map. the core library doesn’t provide an exact tool for this, but you can easily write your own using reduce or for.

lilactown15:05:49

you could even give it a name, update-when:

(update-when #(= (:id %) 2) assoc :foo "bar")

orestis16:05:00

There’s nothing Clojure specific here. You have a bunch of random stuff in a container, without an index you have to walk the container and find the one you care about.

Aron18:05:49

of course there is, it's specific to clojure that this is hard to do, when everything else is easy

Aron18:05:02

in js you just do .findIndex or .indexOf

andy.fingerhut16:05:11

or, if you know you want to do lots of updates, and not have to walk the container sequentially for each update, then use a hash-map or similar thing that lets you find elements by their id much more quickly than linear scan.

Aron19:05:04

for you, I repeat : ), I need to update this data and I need to send it to a backend server, it's not a question of how to represent something, otherwise I would be using something that's better already and I wouldn't be in this position in the first place

Aron19:05:28

I can use different data structures that indeed make this operation easier

Aron19:05:48

but then I have to introduce separate process just to (de)serialize

didibus17:05:11

@ashnur There are some libs specialized in these query like things when you don't want to write your own reduce. The best one I know which allows write queries as well is Spectre

didibus17:05:48

Also in your case, it sounds like a use case for map or for. Since you don't intend to grow or shrink the input collection. You only want to possibly modify each element in the collection

Aron18:05:00

@didibus I do intend to both grow and shrink the collection, just not during this operation. But again, it's more about trade-offs. I can either convert it to a hashmap so it's easy but then I have to convert it back to a vector later, or I can write some complicated helper function to use which then leads to incidental complexity. I can not be as casual about this as some of the responders seem to expect me to be : )

didibus19:05:51

Maybe I'm not following the conversion, is A a vector as well?

didibus19:05:32

Also, if you have a vector, and want to use named keys, it's possible to do something like:

(def person ["John" 13 "4.2"])

(let [name #(get % 0)
      age #(get % 1)
      height #(get % 2)]
  (name person))
This gives the idea. But you can create named getters for the elements. So you have a nice mapping between name and a better that can extract the right position out.

didibus19:05:58

You can make yourself setters as well:

(defn make-person
  [name age height]
  [name age height])

(defn get-name
  [person]
  (get person 0))
 
(defn set-name
  [person name]
  (assoc person 0 name))

didibus19:05:19

You can also just map names to index like:

(def name-idx 0)
(def age-idx 1)
 
(get person age-idx)
(assoc person name-idx "Bob")

Aron19:05:53

no, A is hashmap

Aron19:05:14

the goal is to have a vector of hashmaps, one of them updated, but technically I don't have to store the data in that format, I could just serialize to it at the end before I send it over the wire

thheller19:05:41

@ashnur why do you dismiss reduce when it is a good solution? reduce-kv would be even better, probably best performance wise

Aron19:05:44

I don't necessarily, I just want to be absolutely sure I don't start using it indiscriminately, mostly because with reduce I can do everything, I used a lot in js, and I don't want to keep js habits for cljs code

thheller19:05:07

why not? reduce is a good thing, I use it everywhere. probably one of the most fundamental functional things. every functional lang has it, often called fold though

💯 4
didibus19:05:13

So why not:

(def updates
  {:123 {:keys [:a :b :c]
         :val 25})

(mapv
  (fn[a]
    (let [transform (get updates (:id a)
          type (:type transform)
          keys (:keys transform)
          val (:val transform)]
      (if transform
        (assoc-in a keys val)
        a     
  vec-of-a)

Aron19:05:56

it's more about the js habits than reduce. If there is a good solution that is special to cljs, I will never see it because I already wrote some spaghetti code for that

didibus19:05:40

Ya, with regards to that I'd say Specter is one common approach, needed when you have powerful requirements. Otherwise, it is often possible to compose transducers or sequence functions in order to achieve a particular looping behavior without resorting to a loop. That said, it's not always possible, and sometimes you need to resort to a loop. Map and reduce are halfway between a loop and higher level sequence functions though. So still better then a full on loop/recur. And a recursive loop is also better then an imperative mutable loop. So there's a big spectrum here.

Aron19:05:07

I used the indexOf trick (update-in state-map [:vector-data (first (keep-indexed #(when (= (.id %2) curr-id) %1) vector))] value

Aron19:05:13

probably reduce is better : D

Aron19:05:45

out of all cljs users, how many do use specter regularly, like in a ratio or percentage

didibus19:05:13

Well, honestly your example seems trivial, so Specter is probably overkill.

Aron19:05:16

because I remember watching on youtube, and it's like lenses or similar, "now I have 2 problems"

Aron19:05:29

ok, thanks, that's good to know : )

didibus19:05:54

Its not like lenses, it is on functionality, but it uses a different approach. Personally I find Specter easier to use then lenses.

didibus19:05:42

Specter would be good if you needed to do a lot of really nested queries of big data-structures with ton of nested stuff, and you need to query or update certain parts

didibus19:05:06

The downside in ClojureScript is one more thing to bundle with your app, increases bundle size

Aron19:05:56

specter one more dsl and the code doesn't warrant the specificity imho

didibus19:05:11

Also, just one last thing, converting data structure from one another is pretty idiomatic. So like, convert to a more natural structure for transforming, and then convert back. Unless you start seeing performance issues around it (rare), it's totally fine to do that and helps having cleaner code a lot.

thheller19:05:20

turning this into a map first is completely wasteful. don't do that if you only do a single update

didibus19:05:20

And in fact, converting to more appropriate data-structures and back can sometimes help performance even, if it becomes a more optimal structure for the following manipulation

Aron19:05:02

how can I short circuit reduce-kv?

Aron19:05:31

thank you

didibus19:05:38

You can also use get and assoc and update on vectors by the way. Vectors are indexed too, just by integer instead of key.

Aron19:05:28

didibus, that's what I was doing, but update-in requires .indexOf or .findIndex, that's where I stopped reading the docs and came here to ask my questions : )

didibus19:05:20

Hum... Maybe that's some ClojureScript specific thing. I'm more of a Clojure dev so never heard of this

Aron20:05:22

sounds more like a (human-) language problem

Aron20:05:26

so, I started thinking about how I would have to do this with reduce-kv, and it's weird because I have to do 2 things, until I find my target, I need to conj I think? And then update the value, then conj both the value and the rest of the vector wrapped in a reduced call?

thheller20:05:39

do nothing if its not the object you are looking for, just return the value you got

thheller20:05:39

(defn merge-for-id [the-vec the-id m]
  (reduce-kv
    (fn [the-vec idx {:keys [id] :as v}]
      (if-not (= id the-id)
        the-vec
        (-> the-vec
            (update idx merge m)
            (reduced))))
    the-vec
    the-vec))

(merge-for-id data 123 {:new "value"})

Aron21:05:24

this is what I mean, all kinds of incidental complexity pops up, I am sure this is very simple code for clojure/script devs, but for me it seems weird that the language that says that if I adopt it, my life will be easier because I can use core functions for everything, can't do something admittedly trivial without a couple of custom functions

Aron21:05:22

anyway, dead horse thoroughly beaten : D

thheller21:05:46

so you expect core to solve every possible problem for you? I don't know a single language where that would be the case. I can think of at least 10 different ways of solving this particular problem, each with its own trade-offs so you are going to have one the works best for you and your use-case. that is not something core could possibly ever do.

Aron21:05:27

no, not every single problem, why are you saying something outrageous like that? I clearly specified that my expectations were about something "admittedly trivial", which is under no circumstances "every single problem".

thheller21:05:28

this is not a "admittedly trivial" problem as you make it out to be. what if you have multiple matches in a vector? what if you vector has a million elements? what if it has 5? what if you don't need to check the key via = but use some other predicate? what if you want to batch updates to avoid traversing the entire vector multiple times?

thheller21:05:49

specter is a good example of a library that tries to solve these kinds of things in a more generic way but you have to pay for it in code-size ... again a trade off that doesn't make sense for all apps

thheller21:05:30

if you are going to update things often by id it makes sense to have a structure of {id {:id ... :key "val" ...}} to begin with. if you care more about the ordering and rarely update by id then a vector is better suited

Aron21:05:32

let's not argue about definitions, someone else said the problem seemed trivial, I believed them, if you say it isn't, I can believe you too

Aron21:05:59

that's what I started with though, it's like people only react to half of what I write : )

Aron21:05:14

I did ask my question in the beginning, should I convert everything or should I write helper functions

Aron21:05:50

because I have to produce a vector of hash-maps but I can see it's not the best way to deal with them.

Aron21:05:25

In fact, the actual JSON I have to send is {rootkey: { stuff: 'whatever', someVector : [ { somekey: "with values", actualVectorIamAskingAbout: [ {id: 1, nested: data}, {id:2 nested: data2 } ] } ] } }

thheller21:05:27

we do not know enough about the problem you are trying to solve. the answer whether you should convert or not depends on your requirements and code. if you have a million items in the vector and want to update one element turning it into a map first is extremely wasteful.

Aron21:05:58

that's understandable, I don't expect people here to ship my solutions. And if I ask a bad question, I can face this fact : )

Aron21:05:09

it's a small vector, technically the whole structure is a waste of time and space. With some refactoring it would be actually trivial to deal with it, but this api is some kind of demonic device, it's like someone decided to implement a custom rpc over django rest framework

thheller21:05:38

yeah I have dealt with my fair share of structures like that. no fun.

Aron21:05:59

I have high hopes that if I get good at clojure I can avoid at least some of this

thheller21:05:54

I can confirm that clojure takes some of that pain away 😛

thheller20:05:44

something along those lines

thheller20:05:30

note that this only works if you are sure you have a vector, it will break if its not a vector

thheller20:05:45

and you can write this more generically of course

didibus20:05:54

Anyone can suggest a good succinct tutorial for CSS geared at experienced developers?

didibus20:05:14

Something that explains the evaluation model and how the styles combine and all that

didibus20:05:17

And maybe with resources in this format to describe the features: https://yoksel.github.io/flex-cheatsheet/ I found this explained flexbox really well

Aron21:05:18

http://css-tricks.com have good entries for any topic

Aron21:05:52

how styles combine is extremely easy, there is a hierarchy of specificity

Aron21:05:58

and they Cascade : )