Fork me on GitHub
#beginners
<
2021-10-28
>
Lycheese09:10:46

Is there a way to use destructuring in defrecord? I tried (defrecord Gene [& {:keys [symbol aliases transcripts]}) but it complains that the fields must be symbols. Is there a way around that? Or should I just use a separate function as constructor?

delaguardo10:10:54

With defrecord you declare which field that record should have. You can't use restructuring here and should use separate function to create new instance

๐Ÿ‘ 1
Casey10:10:05

I just upgraded from the clojure cli 1.10.2.774 to latest 1.10.3.x , and it is astounding how much faster the tool is. Just running clojure -h is an order of magnitude faster it feels.

Alex Miller (Clojure team)12:10:21

Thanks for saying that! I spent a lot of time on perf improvements recently.

1
Lycheese11:10:30

Typos for the win Sry about the post

Lycheese12:10:04

I'm developing a full-stack Clojure Webapp and followed "Web Development in Clojure, 3rd ed." for the underlying structure. In the process of expanding and changing this to suit my needs I am serving quite large and complex data in the form of maps from the backend (I have written a loose spec for most of them) and then processing them in the front-end (mainly as an input control using a custom typeahead). Retrieval of values is done by just calling the maps with the respective keywords of the data, resulting in quite a bit of duplication in the front- and backend. After watching a few of Rich Hickey's talks and working my way through Joy of Clojure I've become unsure of whether this is a good approach. What I want to ask is: Is it good practice okay to have the locations of my data defined multiple times in my application when a spec controls whether they conform to that shape? If not, should I put that shape in cljc files or is there a better way of declaring it? (polymorphism?) Edit: the maps mentioned above are actually vectors of maps where the maps represent entrys of a category (e.g. a specific gene in a list of genes)

Alex Miller (Clojure team)12:10:03

One of the big benefits of clojure.spec or other such tools in Clojure is that the same spec can be defined once in cljc and used from both

Lycheese12:10:57

But if I need to change the spec I also need to change all places where I used a part of the spec that was changed. It seems to me the larger my application becomes the more painful changes in this system become. Should I replace the keywords with functions and define those functions all in one place? Would it be better at that point to use records and protocols?

Alex Miller (Clojure team)12:10:35

All of those ideas will require you to change things at multiple places. Adding layers just makes more places

Alex Miller (Clojure team)12:10:58

If you change your data, you have to change your program, that's just a consequence

Alex Miller (Clojure team)12:10:36

The key is to stop making breaking changes and make additive changes

Alex Miller (Clojure team)12:10:57

Don't change the type of an attribute; add a new attribute

Alex Miller (Clojure team)12:10:21

Don't remove an attribute, just stop using it

Alex Miller (Clojure team)12:10:32

Whenever possible, let data flow in maps and spec only what is required (let unknown attrs flow)

Alex Miller (Clojure team)12:10:26

Build systems made to grow - Clojure gives you a ton of tools for this

Lycheese12:10:44

I think I understand what you're saying and the implications. At least it feels like the knot in my thought process is loosening.

Alex Miller (Clojure team)12:10:50

Having built big systems in both ways, there is no question that I prefer making the data as direct as possible with no layers between the program and the data

Alex Miller (Clojure team)12:10:14

As Rich once said, the only thing you can do to data is ruin it :)

Lycheese12:10:04

Time to stop using some of the layers I have built then Thank you for your help^^

sheluchin13:10:09

Is there some core function that does something like (? & fns) => [(f1) (f2) (fn) ...]? Take any number of functions and return the result of calling all of them in order?

Alex Miller (Clojure team)13:10:21

((juxt f1 f2 f3)) would work

Alex Miller (Clojure team)13:10:27

or ((apply juxt fns)) if you have a coll of fns. although calling a lot of no-arg fns seems weird and implies a lot of side effecting code :)

sheluchin13:10:59

Of course! I even made a note of juxt last time I saw you mention it here ๐Ÿ™‚ hard to remember the right one to retrieve from my brain without a bit of practice.

Alex Miller (Clojure team)13:10:54

there are only a few function-making functions (juxt, comp, partial, every-pred, some-fn, constantly, ...)

sheluchin13:10:18

About that side-effect point... I'm not sure whether this qualifies or not. I want to run a bunch of git queries with sh calls and return the result. I understand that :out is a side-effect because it's technically printing somewhere, but I really just want the result and don't care about the side-effect portion of it. Am I being pragmatic by considering these as operations without side-effects for this use case or is there a better way?

1
Alex Miller (Clojure team)13:10:31

run! is another option here and is intended for side effecting things

Alex Miller (Clojure team)13:10:12

(run! #(%) fns) I think ?

sheluchin13:10:45

It's just unclear to me whether I should be considering sh output as a side-effect here. I need the result returned in a data structure and the sh call doesn't make any changes besides printing to stdout. A silly simplified example would be to echo 1 && echo 2 and collect the results as [1 2]. The side-effecting functions wouldn't give me access to those results because they all return nil (I think..).

Alex Miller (Clojure team)14:10:18

yeah, if you need the result then run! won't help you

sheluchin14:10:44

Maybe I should try to defer these operations and concentrate them with storing the results to local storage so the side-effects aren't quite as scattered throughout the code. Not sure. Good enough for now, but I'll give it some more thought. Thanks for the advice @alexmiller.

Stuart14:10:31

Is their a built in way to do this:

(let [selected-items @(rf/subscribe [selector])
      selected-items (if (nil? selected-items) #{} selected-items)]
  ;use selected-items without throwing null error
)
?

adi15:10:37

In addition to alexmiller's suggestion (which generalises to any number of let-bound things), if-let or when-let can be more concise if you frequently find yourself needing to bind just one thing. But like he suggested in the other thread, it would be even better to write code that does not need to handle nil values specially.

Alex Miller (Clojure team)14:10:52

(let [selected-items (or @(rf/subscribe [selector]) #{})] ...)

๐Ÿ‘ 1
Apple14:10:25

Or even move this logic to within reframe

Stuart14:10:08

What do you mean? Should I put it into the subscription ? So the subscription always returns either the set or an empty set.

Apple14:10:57

(rf/reg-sub
 :xyz
 (fn [db _]
   (or ......... #{})))

Stuart14:10:20

cool, i wasn't sure if it was OK or not to put logic into subscriptions

Lycheese14:10:03

Try to put logic in layer 3 subs

Lycheese14:10:23

That saves on potentially expensive operations

Alex Miller (Clojure team)14:10:27

I think the best would be to make the code that handles the subscriptions work well with nil (as most Clojure non-interop does)

Alex Miller (Clojure team)14:10:41

but there may be concerns there I don't understand

lepistane16:10:51

is this idiomatic? I feel like i am 'breaking' rules I created function call from string. ((symbol (format "%s-ns/function" ns-prefix)) arg1)

๐Ÿ™Œ 1
respatialized16:10:13

resolve is more what you want here, I think: https://clojuredocs.org/clojure.core/resolve

๐Ÿ‘ 1
lepistane16:10:30

i don't see how it helps me?

dpsutton16:10:09

requiring-resolve sounds perfect for your usecase if you want the var that some symbol โ€œpointsโ€ to

lepistane16:10:11

How is resolve different than directly invoking a symbol (effectively invoking a fn) ?

dpsutton16:10:48

symbols cannot be invoked

๐Ÿ‘ 1
dpsutton16:10:53

or int he way you mean

dpsutton16:10:26

('clojure.set/union #{:a} #{:b})
#{:b}

dpsutton17:10:13

because it is looking up the key 'clojure.set/union in the structure #{:a}, not finding it so returning the alternative #{:b}

๐Ÿ‘ 1
dpsutton17:10:37

('inc 1) would explain it as well

๐Ÿ‘ 1
lepistane17:10:58

AAAAH very nice!!!!! i get it what i did worked accidentally because i had require :as something-ns so when i created symbol something-ns/fn-name and invoked it (something-ns/fn-name ...) it was resolved and pointed to the proper var

dpsutton17:10:25

because the reader resolves symbols to the var. which is why the suggestion to use resolve or requiring-resolve is most probably what you want

๐Ÿ‘ 2
lepistane17:10:10

thank you both very much! i learned something new ! ๐Ÿ˜„

lepistane17:10:01

weirdly i was getting java.lang.NullPointerException: when i called this function but from repl it worked. I will resort to case statement sadly

dpsutton18:10:10

were you using requiring resolve or resolve?

dpsutton18:10:43

also that returns a function so you can in a repl just try out the require expression without calling it. youโ€™ll either get the function or nil

lepistane18:10:43

behavior in repl was fine. both resolve and requiring-resolve returned function i expected which called with proper arguments would do it's job but when i attempted to use that code inside an app it would throw exception. resolve would be null pointer requireing-resolve would say that there is NS on classpath

dpsutton18:10:01

can you log the arguments to it to ensure that it is what you expect?

dpsutton18:10:10

also a case statement sounds perfect if its a closed set

dpsutton18:10:24

but you should know why it is failing. and it is most likely the input data

lepistane22:10:28

data is fine, function doesn't get called at all. if i figure it out i will report