Fork me on GitHub
#clojure
<
2021-09-01
>
andy.fingerhut00:09:39

Has anyone created a tool that reads Clojure/JVM stack traces as printed in text form, without any pretty-printer/formatter involved, and then shows them in one of several possible pretty-printed forms?

mauricio.szabo01:09:49

Kinda There's some code on Chlorine that does that

andy.fingerhut00:09:45

I would guess that if a plain old text printed stack trace can be read in a way that produces a stack trace JVM object, you could use any of the existing stack trace pretty-printers on it, yes? So maybe my question is: is there a way to read a plain old text printed stack trace and produce a stack trace object in memory?

hiredman01:09:46

There are exceptions, and from exceptions you can get an array of stacktrace elements, which are trivial to construct

hiredman01:09:56

But exceptions are an open set, so could have all different kinds of behaviors (private constructors, ignore the set stacktrace method, etc)

cfleming07:09:30

Ok, I’m sure I’m doing something really stupid, but I can’t see it in my addled state:

(binding [*compile-path* "/Users/colin/dev/cursive/out/production/cursive"]
  (compile 'cursive.intellij.class))
=> cursive.intellij.class
And then…
~/d/cursive (deprecations)> ls /Users/colin/dev/cursive/out/production/cursive/cursive/intellij/
~/d/cursive (deprecations)>
Any ideas why I’m not getting any classes generated?

thheller08:09:43

@cfleming is the ns already loaded? IIRC it only works for namespaces not yet loaded

2
cfleming08:09:37

Hmm, interesting, let me restart and check.

cfleming08:09:31

That was indeed the problem, thanks!

👍 4
dpsutton13:09:16

is there a good idiom for conditional doto?

(doto (DecimalFormatSymbols.)
  (.setDecimalSeparator decimal)
  (.setGroupingSeparator grouping))
handling when decimal or grouping might be nil and omit that expression (statement?)

potetm13:09:20

IMO stdlib should include some version of this. It really is annoying.

cyppan13:09:22

I would wrap it in a let, and put some when inside since it mutates the object anyway

cyppan13:09:32

it would be slightly more verbose I admit

dpsutton13:09:14

@U07S8JGF7 i saw that one with apropos but was hoping there was a clever combination of things from core. @U0CL38MU1 yeah i ended up doing the obvious let when, when, return object just wondering if there was a better idiom. Apparently there isn't and i agree this would be nice to have

potetm13:09:33

Fogus had some clever << macro at one point, and I’m wondering if it would cover this case.

potetm13:09:48

I think it was Fogus. For the life of me I cannot find it.

potetm13:09:08

It was like a cond threader that you could add let into

vemv13:09:55

(cond-> x
  (foo?) (doto inc)
  (bar?) (doto dec))

potetm13:09:27

doesn’t work for setters that return void

vemv13:09:13

damn :) I'm dense lately

potetm13:09:57

No I’ve just done that 1000 times and realized why it doesn’t work.

vemv13:09:57

would double doto work?

(cond-> x
  y (doto (doto z))
  ...)
Can't be bothered to thoroughly check... excuse the noise if it doesn't

Ed13:09:59

cond-> does work

Ed14:09:05

(let [a "1"
        b "2"
        c nil]
    (str
     (doto (StringBuffer.)
       (cond-> a (.append ^String a))
       (cond-> b (.append ^String b))
       (cond-> c (.append ^String c)))))

potetm14:09:47

StringBuffer#append returns the StringBuffer

Ed14:09:48

that doesn't matter

dpsutton14:09:51

but that doesn't matter here

Ed14:09:01

(let [a "1"
        b "2"
        c nil]
    (str
     (doto (StringBuffer.)
       (cond-> a (.append ^String a))
       (cond-> true prn)
       (cond-> b (.append ^String b))
       (cond-> c (.append ^String c)))))

dpsutton14:09:24

that's a good idiom what i was looked for. thanks lost3d

👍 2
Ed14:09:50

doto always passes the same thing through to the next expression, regardless of what each expression returns

potetm14:09:30

ah gotcha, yeah I’ve seen the some-> used for nil checking there

potetm14:09:41

missed the top-level doto

potetm14:09:53

yeah no, that’s good. Thanks man!

👍 2
Ed14:09:54

that'll just check that the object you're using with doto is not null

✔️ 2
sova-soars-the-sora04:09:57

I don't knw if this is is relevant, but there is let? and also the lib swiss arrows https://github.com/egamble/let-else https://github.com/rplevy/swiss-arrows

dpsutton05:09:27

seems like interesting stuff in there but i don't think i'd want to take a dependency on that

potetm14:09:55

Now this is gonna bother me. Does anybody know where that << (or maybe <<-) macro that was going around about a year ago went?

potetm14:09:27

YES. Thank you!

cyppan14:09:23

😲 do you have an example of how you would use it? Don’t really understand the one in the tweet

potetm14:09:39

There’s an example in the twitter thread

cyppan14:09:18

Yes I got it now

potetm14:09:22

It was like -> but you could use arbitrary let-bindings

potetm14:09:31

And it was tiny. Fit in a tweet.

Maravedis14:09:03

I mean... Just go ham with it : https://github.com/rplevy/swiss-arrows

🙅 2
😂 2
😄 2
Noah Bogart14:09:18

that library is so much fun. not useless per se, but certainly not anything i want touching one of my codebases, and yet i love reading through the code and marveling at it

potetm14:09:31

Oh wow! It has <<- ten years before I first saw it!

Maravedis14:09:56

Coming from scala, I really want to use it. The nil-shortcutting diamond thingy is just so good.

potetm14:09:22

I’m curious where you think that’d be valueable.

potetm14:09:44

I’ve literally never had a need for such an animal 😄

Maravedis14:09:15

Well, I find the threading macros frustrating, because collection functions need to use ->> but hashmap functions use ->. The wand thingie is basically as-> but better. And nil shortcutting is pretty cool in an api environment, where inputs can be bad.

Maravedis14:09:26

Also the side effect arrows are nice. Being able to add (-!> println) and go about your debugging is nice.

delaguardo14:09:29

(-> something
  (doto (println)))

Ed14:09:54

-!> sounds a lot like doto

Maravedis14:09:33

I didn't know doto , tbh. I'll look it up.

Joshua Suskalo14:09:14

@U02DEQJ409F the rationale for the two types of arrows is that there are "sequence operations" (what you called collection functions) which take the sequence as the last argument. They are for stream processing basically. And there are collection operations (which the map functions are, along with nth, and a bunch of other similar things), and those take the collection first. Basically if you're trying to transform a stream, you need ->>, but if you're trying to work with an individual piece of data, then you need ->. Also you can combine -> with as-> to make some nice chains even when the first argument isn't what's needed.

2
Ed14:09:45

the threading macros also work together

(-> {:things (range 10)}
      :things
      (->> (map inc) (reduce +))
      (as-> x (* 2 (/ x 11))))
... as Joshua said 😉

Maravedis15:09:39

Wow. Coming here was my best decision today. Thanks for that information. This is literally gonna change my life x)

Jelle Licht15:09:00

https://stuartsierra.com/2018/07/06/threading-with-style was a nice read about the subject as well

👍 2
2
Nikhil Warke16:09:34

https://github.com/Reactive-Extensions was one of the delightful things I found in other languages. Threads have the same spirit, and I find them cleaner/more elegant

Ed16:09:52

I've started to use transducers a lot more where I had previously used ->> which seem to overlap with the reactive extensions ideas from other langs

Michael Gardner18:09:56

I'd use transducers more if they were more ergonomic.

(sequence (comp (map inc)
                (filter even?))
          (range 10))
I find that much more awkward to write, and more importantly harder to read, than a ->> pipeline of normal sequence functions:
(->> (range 10)
     (map inc)
     (filter even?))
And other developers less familiar with transducers will find the difference in readability even more stark. So even though transducers are better for performance and conceptually cleaner, I can't justify defaulting to them

Michael Gardner18:09:14

(of course you can use into instead of sequence, but that doesn't change much)

Ed09:09:01

well ... sequence is lazy and into isn't ... I think that making lazyness optional at the point of application is a great feature of transducers.

Ed09:09:41

I think that familiarity will only come with more usage.

Maravedis10:09:56

I was going to ask : I have to map over a sequence and also sort it. Would a transducer to that in one pass ?

Ed11:09:35

well a lazy-seq won't run through the whole dataset in one go, and you need to realise all the data in order to sort it (cos the last element might be the first one 😉 ) ... but

(transduce (map dec) (completing conj sort) [] (range 10 0 -1))
that will process the sequence, and sort the data at the end, and using the xforms lib you can write this
(require '[net.cgrand.xforms :as x])
  (into [] (comp (map dec) (x/sort)) (range 10 0 -1))
which might read a bit nicer and will let you sort the data in the middle if you want 😉 ... but the transducer version won't create an intermediate lazy seq for each step

Joshua Suskalo17:09:08

Creating a sorted collection will not be particularly efficient whether you use transducers or not. Probably the best way to do it would be to use into with a sorted-set-by as the target and then call seq on the result.

Joshua Suskalo17:09:37

Although cgrand.xforms may be more efficient since it uses java's arraylist sort without sorting the intermediate steps

Ed21:09:25

into a sorted set will remove duplicates which probably isn't what you want. I would have thought that the xforms sort would be most efficient, but test it with your dataset ...

Maravedis07:09:47

Thanks for the recommendations.

rickmoynihan16:09:33

What options are there for implementing a swagger/openapi-v3 server in clojure these days? When I last looked reitit seemed to be one of the best bets; though it still looks like it doesn’t yet support openapi v3. Has the situation on what viable options there are changed at all?

Ben Sless16:09:03

#reitit has a channel, might be best to ask there

rickmoynihan16:09:55

To my knowledge the situation for reitit hasn’t changed. I’m asking whether there are other options now? (Rephrased question to be clearer).

rickmoynihan16:09:50

ahh thanks — I was trying to recall the name of that one — sadly doesn’t look finished nor updated recently 😕

ikitommi17:09:29

just discussed last week about adding OpenAPI3 finally to reitit. There have been many people contributing to that, but not completely done. Just work.

👍 2
ikitommi17:09:16

Malli and spec both have -> JSON Schema translation already.

👍 2
raymcdermott17:09:39

I am performing a read on some clojure and it the metadata is lost during the read (or so it seems) ... I guess I'm missing something simple so would appreciate some guidance

raymcdermott17:09:46

(read-string "(deftype StringReader
  [^String s ^long s-len ^:unsynchronized-mutable ^long s-pos]
  Reader
  (read-char [reader]
    (when (> s-len s-pos)
      (let [r (nth s s-pos)]
        (update! s-pos inc)
        r)))
  (peek-char [reader]
    (when (> s-len s-pos)
      (nth s s-pos))))")
=>
(deftype
 StringReader
 [s s-len s-pos]
 Reader
 (read-char [reader] (when (> s-len s-pos) (let [r (nth s s-pos)] (update! s-pos inc) r)))
 (peek-char [reader] (when (> s-len s-pos) (nth s s-pos))))

hiredman17:09:55

why do you think the metadata is lost?

raymcdermott17:09:41

ah, when I try to eval it it fails

raymcdermott17:09:22

it says that s-pos cannot be changed but ^:unsynchronized-mutable is set there to counter that

raymcdermott17:09:52

so maybe I have to do something to the data to re-surface the meta?

hiredman17:09:19

what is update! ?

raymcdermott17:09:39

it's a macro that calls set!

raymcdermott17:09:50

this is from clojure.tools.reader.reader-types

borkdude17:09:40

it's pretty easy to check if the metadata is still there, just navigate to the symbol and call meta on it

raymcdermott18:09:33

I would like the eval to do that 🙂

borkdude18:09:51

sure, but you're now in the debugging stage

raymcdermott18:09:20

yes, I'm checking whether there are any quick fixes that I'm unaware of

hiredman18:09:22

there is a binding you can set to make pr print the metadata

raymcdermott18:09:42

yes but it doesn't work during read

borkdude18:09:48

quick fixes: first diagnose the problem before quick fixing anything ;)

dpsutton18:09:48

(set! *print-meta* true) shows the meta is there

dpsutton18:09:48

(deftype StringReader [^String s ^long s-len ^{:tag long, :unsynchronized-mutable true} s-pos] Reader (read-char [reader] (when (> s-len s-pos) (let [r (nth s s-pos)] (update! s-pos inc) r))) (peek-char [reader] (when (> s-len s-pos) (nth s s-pos))))

raymcdermott18:09:00

thank you for the wisdom @borkdude... I just meant is there a *retain-meta* type thing on read

borkdude18:09:20

are you evaluating this in the right namespace context? are you sure update! resolves to that macro?

2
raymcdermott18:09:23

oh, ok maybe its a binding I do when printing to the prepl

raymcdermott18:09:42

yes, I think that's it ... quick fix 🙂

raymcdermott18:09:02

I'll give it a spin and get back to you ... thanks

raymcdermott18:09:32

💥 yes that works

dpsutton18:09:12

what worked? printing meta or figuring out why some code is broken?

Ed18:09:45

I'm guessing running eval in the right namespace

raymcdermott18:09:00

printing the meta ... I hadn't set that on when writing to the prepl

raymcdermott18:09:33

so as @hiredman suggested ... the metadata was still there but I wasn't sending it effectively

raymcdermott18:09:13

@l0st3d the namespace was OK

raymcdermott18:09:04

FYI I was evaluating the whole ns so that was always going to work but without context it was a good suggestion

Ian Fernandez18:09:48

Is there a way on argue that it's better to not have nil values in a map when you're parsing some data?

Ian Fernandez18:09:05

right now I've made a function that parses CSV's

Ian Fernandez19:09:17

(parse-csv
  (str "column-1,column-2,column-3\n"
       "row1-a,row1-b,row1-c\n"
       "row2-a,,row2-c\n\n\n"))
=>
[{:column-1 "row1-a", :column-2 "row1-b", :column-3 "row1-c"}
 {:column-1 "row2-a", :column-2 "", :column-3 "row2-c"}
 {:column-1 ""}
 {:column-1 ""}]

Ian Fernandez19:09:33

instead of having blank strings or nil's when the blank strings came

Ian Fernandez19:09:43

I think that a better default would be like

Ian Fernandez19:09:00

(parse-csv
  (str "column-1,column-2,column-3\n"
       "row1-a,row1-b,row1-c\n"
       "row2-a,,row2-c\n\n\n"))
=>
[{:column-1 "row1-a"
  :column-2 "row1-b"
  :column-3 "row1-c"} 
 {:column-1 "row2-a"
  :column-3 "row2-c"}]

Michael Gardner23:09:49

IIRC Rich has expressed a preference for omitting keys, and I think that's reflected in the design of at least clojure.spec

Ian Fernandez19:09:50

some people is saying that I'm missing some information when I do that

Ian Fernandez19:09:01

for me as myself I don't think so

Ian Fernandez19:09:13

some argument about it on clojure would be

potetm19:09:30

“I have this data, and the value is nil” vs “I don’t have this data”

Ian Fernandez19:09:39

(->> [{:a 1} {:a 1 :b 2}]
     (map (fn [{:keys [a b] :or {b 0}}]
            {:a (inc a)
             :b (inc b)})))
(->> [{:a 1 :b nil} {:a 1 :b 2}]
     (map (fn [{:keys [a b] :or {b 0}}]
            {:a (inc a)
             :b (inc b)})))
Error printing return value (NullPointerException) at clojure.lang.Numbers/ops (Numbers.java:1068).
Cannot invoke "Object.getClass()" because "x" is null

Ian Fernandez19:09:52

the fn in map would be the one that does the parse

Ian Fernandez19:09:59

instead of validating stuff only

potetm19:09:27

Yeah :or doesn’t work like most idomatic clojure. (On the same premise that I said before.)

Ian Fernandez19:09:51

didn't got what you mean by that

Ian Fernandez19:09:02

"doesn’t work like most idomatic clojure."

potetm19:09:58

most people do (when-some [v (get m :key)] …) — which is only doing nil checking

😢 2
potetm19:09:04

not contains? checking

potetm19:09:15

but :or does a contains? check

Ian Fernandez19:09:09

I don't think this is idiomatic

potetm19:09:44

I mean, I dunno what you want me to say.

potetm19:09:55

It is. Doesn’t mean you have to like it 😄

potetm19:09:02

There are lots of idioms I don’t like.

Ian Fernandez19:09:12

:thinking_face:

potetm19:09:32

and :or does a contains? check because there’s a difference between “I have this value and it’s nil” and “I don’t have this value”

Ian Fernandez19:09:08

then why not have only data that does not have nils?

Joshua Suskalo19:09:50

using :or only makes sense when there's a sensible default value which the user always wants when calling that code, and where it's invalid to not have that data. It should not be used to provide default data for when there is missing information.

Ian Fernandez19:09:52

then it should be better to have empty strings? not nil?

potetm19:09:47

Why? So that you can have two values for nil you have to check?

potetm19:09:28

,, in a CSV means either: I don’t have this data or I have this data and the value is nil. Given the semantics of CSV it’s better to assume the latter and move on.

potetm19:09:19

(I know from experience that using "" leads to problems. For CSVs, I usually trim all whitespace and then translate "" to nil.)

Ian Fernandez19:09:28

why then you would have keywords with nil values?

Joshua Suskalo20:09:10

To say that you know what value is for that keyword, and the correct value is nil

Joshua Suskalo20:09:26

Which is distinct from not knowing what the value for that keyword is.

potetm19:09:46

Does that make sense?

potetm19:09:12

And does that answer your question?

Harshit Joshi19:09:21

hey everyone, i am a beginner

(defn func
	([] sentence)
	([name] (str sentence name)))

(func "harshit")

Harshit Joshi19:09:28

is this code wrong ?

dpsutton19:09:53

there's a #beginners channel with lots of people who will take the time to work through it with you

2
👍 2
Harshit Joshi19:09:21

oh thanks man

👍 2
Joshua Suskalo19:09:03

On the CSV parser, I think it's valid to have nil values in the map, specifically because it's parsing tabular data. In this format there is no distinction between "we have this data and it's null" and "we don't have this data" so it's better to preserve the semantics of the original format, which is that every row has every field, and some may be nil. In general though, if you don't have information for a particular field of some domain object, just don't include the key. This gives you strictly more information than always storing it with nil does. If you always have nil values for specific keys then you're effectively emulating place-oriented-programming with maps.

2
☝️ 6
2
noisesmith19:09:23

one reason we need to be able to have nil under a key: removing a key from a record can change its type to hash map, setting the value to nil does not do this

🎯 4
noisesmith19:09:12

(ins)org.noisesmith.art.audio=> (defrecord Foo [a b])
org.noisesmith.art.audio.Foo
(ins)org.noisesmith.art.audio=> (type (dissoc (->Foo 1 2) :a))
clojure.lang.PersistentArrayMap
(ins)org.noisesmith.art.audio=> (type (assoc (->Foo 1 2) :a nil))
org.noisesmith.art.audio.Foo

noisesmith19:09:48

also, more generally, it is possible to put a nil into any place an Object is expected in the jvm, it would be awkward to create an exception to this

noisesmith19:09:22

@d.ian.b is your question "why is it possible" or "why would you leave data in this state"?

Ian Fernandez19:09:07

=> why would you leave data in this state if you could not have nil? this one

noisesmith20:09:29

a helper function like this is relatively common

(ins)org.noisesmith.art.audio=> (defn canonify [m] (into {} (remove (comp nil? val)) m))
#'org.noisesmith.art.audio/canonify
(ins)org.noisesmith.art.audio=> (canonify {:a nil :b 1 :c nil :d 2})
{:b 1, :d 2}

noisesmith20:09:45

(though probably with a different name)

Alex Miller (Clojure team)20:09:13

these days, I typically put the effort into avoiding putting the nil in in the first place

👆 4
Alex Miller (Clojure team)20:09:30

much like the "bag of relevant attributes" approach taken in spec, datomic, etc (and inspired by similar approach in RDF), I think this is usually the best default approach in Clojure. it's not always the best - sometimes it helps to have "regular" rectangular maps (and CSV and database tables are places where this can come up) so warrants some intentional thought up front

raspasov20:09:31

One idiom I’ve started recently using is using (or …) after destructuring instead of the destructure :or

(let [{:keys [a b c]} {:a 1 :b 2 :c nil}
      c (or c 42)]
 c)
;=> 42

Alex Miller (Clojure team)20:09:29

this is exactly the purpose of :or in destructuring

Alex Miller (Clojure team)20:09:48

(let [{:keys [a b c] :or {c 42}} {:a 1 :b 2 :c nil}]
 c)

Alex Miller (Clojure team)20:09:35

oh, I see what you mean, nvm

👍 2
raspasov20:09:45

But I also usually avoid having maps with a [:c nil] entry unless really needed.

noisesmith20:09:19

as long as false isn't a reasonable value for :c

☝️ 6
seancorfield20:09:23

Re: database and nil -- next.jdbc has a next.jdbc.optional namespace with result set builders that omit nil-valued keys (as opposed to the regular builders that leave them in) so you have a choice -- precisely for this sort of reason.

clojure-spin 4
seancorfield20:09:04

You need nil values in hash maps for UPDATE and sometimes for INSERT but it can be a lot cleaner to avoid them for SELECT operations.

marciol20:09:58

@alexmiller reminds me the Maybe Not talk > We do not, in Clojure, tend to do this using nulls, right? We do not put a key in our map, and put nil in as the value there. And I am going to talk about the differences there. https://github.com/matthiasn/talk-transcripts/blob/master/Hickey_Rich/MaybeNot.md

raspasov20:09:03

@marciol Yes! Semantically, it doesn’t make sense, does it? “I’m setting the value of this thing, and the value is … nothing!”

raspasov20:09:06

It sounds weird 🙂

marciol20:09:45

I think that this is the point raised by @d.ian.b

🚀 2
raspasov20:09:10

Definitely… If I’m getting rid of the value under a key, I use (dissoc …) There can be edge cases like in the case of defrecord like pointed out by @noisesmith, but that’s not the typical case.

☝️ 2
isak20:09:49

Is it possible to do this in a library, or has the core team considered adding a way to omit nils in collection literals? E.g.,

[:a :b #omit-nil (function-that-will-return-nil)] => [:a :b]

Ed20:09:14

I would have thought that wouldn't work, because the #omit-nil bit is a reader tag and the function will need to be run at runtime, after it's been read and compiled ... Right?

isak20:09:46

You are probably right, I have a gap in my knowledge, never looked into reader literals at all

isak20:09:43

{:a "A" :b #if-some (function-that-will-return-nil)} => {:a "A"}

seancorfield20:09:56

@isak That sounds like mixing compile-time and runtime concerns?

seancorfield20:09:23

(let [x (function-that-will-return-nil)] (cond-> [:a :b] (some? x) (conj x))

isak20:09:25

@seancorfield Well it could just compile to a different expression, for example conditionally assoc! on a transient map, no?

isak20:09:00

And yea I've used alternatives like that before, also made some functions like assoc-some, but it seems more cumbersome than it needs to be

raspasov20:09:36

I am not 100% sure either, but that doesn’t sound like it would be even possible. Pls correct me if I’m wrong. How would the compile-time phase know what (function-that-will-return-nil) returns in order to clear that :b key?

isak20:09:11

It doesn't have to know it, it would compile to an expression that would check before associng. For example:

{:a "A" :b #if-some (function-that-will-return-nil)}
=> 
(let [m (volatile! (transient {:a "A"}))]
  (when-some [b (function-that-will-return-nil)]
    (vswap! m assoc! :b b))
  (-> m deref persistent!))
@raspasov

clojure-spin 2
raspasov20:09:41

Ah, got it. Perhaps instead of that weird #if-some syntax, do a macro like: (without-nils {:a "A" :b (function-that-will-return-nil))})

raspasov20:09:35

You can use Clojure’s metadata feature to identify values/fns to be checked for nil

raspasov20:09:39

(without-nils 
 {:a "A" :b ^:no-nil (fn [x] nil)})

isak20:09:17

Hmm yea, though it would require IMetas

raspasov20:09:53

The without-nils macro would transform the code:

{:a "A" :b ^:no-nil (fn [x] nil)}
into some variation of your code above:
(let [m (volatile! (transient {:a "A"}))]
  (when-some [b (function-that-will-return-nil)]
    (vswap! assoc! :b b))
  (-> m deref persistent!))

raspasov20:09:50

IMO, this feels like too much magic, but I believe it’s possible 🙂

raspasov20:09:04

The easier way:

(defn clear-nils
 "Remove the keys from m for which the value is nil"
 [m]
 (apply
  dissoc m
  (for [[k v] m :when (nil? v)] k)))

raspasov20:09:46

Have used that function, once in a blue moon… But again, I try to avoid having the nils there in the first place.

isak20:09:50

One thing that may be annoying is getting it to work recursively

raspasov20:09:10

A bit, yes.

raspasov20:09:21

But possible.

raspasov20:09:46

Perhaps clojure.walk is your friend then.

raspasov20:09:35

You can walk randomly shaped/deep maps and data-structures and get rid of whatever you want as you walk them.

isak20:09:29

Yea I have done that approach too, works pretty well, because then you can take it a bit further and remove e.g., vectors that become empty after the steps above

raspasov20:09:35

Again, I prefer not having that problem in the first place, if possible. If I control the data/input, I would very much prefer that option 🙂

isak20:09:45

Yea, I was just proposing this as a more concise way to control it, because every alternative we've discussed is still more verbose

raspasov20:09:03

Right… Same rule applies, imo. Clean up the vector in the first place, if you can, or make the code work without caring whether the vector is empty or nil, etc.

isak20:09:50

I think where it comes up is if you are building something like a query AST, where some of the parts are dynamic. For example in some test code, I have this:

:venia/queries
               [[:employees
                 (if first-n
                   {:first :$n :after :$cursor :orderBy :$order}
                   {:last :$n :before :$cursor :orderBy :$order})
                 (filterv
                   some?
                   [page-info
                    [:edges
                     [[:node [:dbId :description]]]]])]]
Where page-info is a binding that may be nil

raspasov20:09:59

Where is page-info declared/coming from?

isak20:09:25

It is just getting destructured from a map argument to the function

raspasov21:09:05

Got it, and it causes a problem if it’s nil?

isak21:09:40

Yea, it is not a valid selection node in the syntax

raspasov21:09:08

Doesn’t the (filterv some? …) clear it up?

isak21:09:34

Yea it does, it is just pretty verbose compared to this:

:venia/queries
               [[:employees
                 (if first-n
                   {:first :$n :after :$cursor :orderBy :$order}
                   {:last :$n :before :$cursor :orderBy :$order})
                 [#if-some page-info
                    [:edges
                     [[:node [:dbId :description]]]]]]]
Especially if it comes up multiple places in the query.

raspasov21:09:49

Is it really that verbose though?

raspasov21:09:31

But I understand multiple places.

isak21:09:54

It is an extra level of indentation usually for a very trivial thing

raspasov21:09:10

Right… I actually might have it like:

[[:employees
  (if first-n
   {:first :$n :after :$cursor :orderBy :$order}
   {:last :$n :before :$cursor :orderBy :$order})
  (filterv some?
   [page-info
    [:edges
     [[:node [:dbId :description]]]]])]]

isak21:09:24

In Cursive, it gets indented like this:

[[:employees
  (if first-n
    {:first :$n :after :$cursor :orderBy :$order}
    {:last :$n :before :$cursor :orderBy :$order})
  (filterv some?
           [page-info
            [:edges
             [[:node [:dbId :description]]]]])]]

isak21:09:51

Since the function arguments 'should' have the same level of indentation

raspasov21:09:04

Yes… I use cursive as well… I have modified the indentation configs

raspasov21:09:10

Another option is to avoid the execution from the top:

(when (and page-info etc-info)
 ;query
 )

isak21:09:46

Right ok, I probably should look into doing the same because it is annoying

raspasov21:09:26

Yea, I write a lot of React(Native) view code. That’s why I reduced the indentations 🙂

2
raspasov21:09:55

With deeply nested ASTs, combined with conditionals/filter etc, I like less visual nesting

isak21:09:19

yea it is nice if indentation is reserved for something really important

raspasov21:09:20

Otherwise the AST code starts drifting to the right, like a lot.

raspasov21:09:22

Screen Shot 2021-09-01 at 2.13.08 PM.png

raspasov21:09:01

Notice how the 3rd/4th line is indented

raspasov21:09:29

That was not the default, I believe. It would indent under the {} and everything drifts.

isak21:09:52

yea exactly. That seems better

raspasov21:09:35

Also, 1 space indent… Definitely non-standard but I like it.

raspasov21:09:50

View/AST code is really different from regular Clojure code. It just makes little sense to split it up into short fns, like one would regular Clojure(Script) business logic code.

isak20:09:16

All the information required to know to compile it that way instead of the static constructor way would be there at compile time, no?