Fork me on GitHub
#clojure
<
2024-02-16
>
flowthing07:02:21

Is there any tool in the Clojure ecosystem that formats maps and let-bindings the way Cursive does by default, apart from Cursive? That is, like this:

;; Maps
{:a  1
 :bc 2}

;; let-bindings (although it seems Cursive no longer does this by default)
(let [a  1
      [b c] [2 3]
      de 4])
Life is way too short to spend doing this formatting manually.

flowthing07:02:59

I know cljfmt has had this feature request for years but I don't believe it's landed yet.

cfleming08:02:15

You have to enable that for let bindings in Cursive, right. It’s a surprisingly hard problem though, which isn’t amenable to a streaming solution which several of these tools (fipp, not sure about cljfmt) use.

flowthing09:02:48

Gotcha. 👍 Perhaps zprint knows how to do this because it seems extremely configurable (to the point of being overwhelming): https://github.com/kkinnear/zprint/blob/main/doc/reference.md#a-note-on-justifying-two-up-printing But I'm looking specifically for something that would format things the way Cursive does, and I'm not sure whether zprint can be configured to e.g. justify let-bindings exactly the way Cursive does, but not cond etc. (which Cursive doesn't justify, I believe).

flowthing09:02:17

Need to look into it.

cfleming00:02:34

Cursive will (optionally) also format cond, assoc etc at some point too.

flowthing20:02:46

Thanks, that's good to know.

agorgl12:02:43

Having a vector of maps that have a :name key, how can I insert another map before the element that has a specific :name in a clojure idiomatic way? So far the best I could think is finding the index of the matching element with keep-indexed splitting it with split-at and concating the new element in between

p-himik12:02:25

One could argue that the most idiomatic way to handle it would be to use a more appropriate data structure. One potential candidate here is a tree map where keys are the order. They can't be integers since you can't insert a new integer between, say, 2 and 3 without shifting the rest of the tree. They might be doubles, but not really if the chance of inserting a lot of items between two particular items is rather high (you can't infinitely divide a double number by 2).. But they can be strings. It's always possible to find a string that, when sorted together with two other given strings, will be right between them. It's not possible to find a string that goes before an empty string, so inserting at the start of the tree will need not just an insertion but also an update to shift that initial empty string a bit. But if you'd rather stick to a vector, I'd use into and subvec. Also consider having an index map like name->idx so that you the search isn't O(n).

agorgl12:02:21

Yeah, I don't have control over the structure

p-himik12:02:30

Ah, I'd go with a loop then. Or into + mapcat that's passed a function that always returns a single-element vector unless it's element in question is the right thing - that it returns two elements, one for what you want to insert and another is the original element:

(into []
      (mapcat (fn [item]
                (if (the-right-item? item)
                  [my-item item]
                  [item])))
      items)

agorgl12:02:28

Damn way cleaner from what I had which is this:

(let [{interceptors ::http/interceptors
       resource-path ::http/resource-path} service-map
      idx (->> interceptors
               (keep-indexed #(when (= (:name %2) ::middlewares/resource) %1))
               first)
      [left right] (->> interceptors
                        (split-at idx))]
  (concat left [(middlewares/fast-resource resource-path)] right))

tomd13:02:19

Agree that mapcat does a fine job here, but if you're requiring medley anyway and want an alternative, point-free approach:

(let [input [{:a 1} {:b 2} {:c 3} {:d 4}]]
  (into [] (comp (m/partition-before :c) (interpose [{:x 42}]) cat) input))
;; => [{:a 1} {:b 2} {:x 42} {:c 3} {:d 4}]

reefersleep12:02:49

Playwright for Java or Playwright for js - which one to pick? I'd like to call it from either Clojurescript or Clojure to do some testing of our frontend. There's a Clojure Playwright wrapper called https://github.com/pfeodrippe/wally , but very alpha and development is not very active, it seems. No known CLJS wrapper. We could iterate on either platform and either expand on Wally or build gradually roll our own wrapper, either in CLJ or CLJS. But I can't figure out whether there's feature parity between the platforms, + maybe other considerations (like ecosystems around particular implementations). For example, I see that js Playwright has visual regression testing, and I can't find the same for the Java version. Of course, there's also the option to just do e.g. js Playwright, no CLJS involved... Initially the least appealing option to me, but if most of the code will be trivial interop calls to Playwright object functions, anyway, and lots of clj->js calls and the like, maybe it's overkill to force CLJS on top.

Leaf Garland12:02:42

We're pretty happy with Playwright java, and some simple wrapper code (mostly type hints) around it. Using the REPL to create tests is great, and we can mix UI tests with server-side code - which we couldn't do with CLJS/JS.

reefersleep14:02:50

Good call, @U02EP7NKPAL. Mixing could be useful.

Ingy döt Net16:02:24

Lately it's happened several times that I want to reach for a cond-let when I have cascading if-let forms. Has that been considered for clojure.core?

kuzmin_m17:02:52

Consider <https://github.com/rplevy/swiss-arrows?tab=readme-ov-file#the-back-arrow%7C&lt;&lt;->

(<<-
  (let [x 'nonsense])
  (if-not x 'foo)
  (let [more 'blah] more)) => 'blah
(defmacro <<-
  "the 'back-arrow'"
  [& forms]
  `(->> ~@(reverse forms)))

👀 2
phill17:02:24

It has been extensively considered! 🙂 ... there might be something about that in the README of https://github.com/Engelberg/better-cond

👀 2
Ingy döt Net17:02:26

Another thing I've been wanting to ask about for a couple weeks is why if-let and when-let only support a single let pair? I've been using https://github.com/yaml/yamlscript/blob/main/core/src/yamlscript/util.clj#L12-L27 (`if-lets` and when-lets ) like crazy lately, but wish that was how if-let and when-let worked. FYI, That code was lifted from the if-let* and when-let* examples in https://clojuredocs.org/clojure.core/if-let and https://clojuredocs.org/clojure.core/when-let

vemv20:02:39

I appreciate Elisp if-let which works like that Maybe it's related to naming, if-let sort of implies there's a single condition. if-and-let (`if-let-and` ?) sounds uglier

Ingy döt Net20:02:50

But let itself supports multiple pairs.

Ingy döt Net20:02:34

It's kind of glaring that the doc pages for them address the multi thing right away. Seems to indicate that it's what people will expect...

p-himik21:02:15

The docs page you mention is not the official documentation - it's a community-driven effort. Also, a lot of cases where people are keen to use if-let can be rewritten with some-> or some->>, which often leads to cleaner code as well (but not always, of course). As an example of what I mean, you have this function in your code:

(defn apply-transformer [key val]
  (if-lets [name (or
                   (get-in key [:Sym])
                   (get-in key [0 :Sym]))
            sym (symbol (str "transform_" name))
            transformer (ns-resolve transformers-ns sym)]
    (or (transformer key val) [key val])
    [key val]))
which can be rewritten as:
(defn apply-transformer [key val]
  (or (when-let [transformer
                 (some->> (or (get-in key [:Sym]) (get-in key [0 :Sym]))
                          (str "transform_")
                          (symbol)
                          (ns-resolve transformers-ns))]
        (transformer key val))
      [key val]))
(As a side note, just in case - beware names like key and val since they shadow the built-ins with the same names).

Ingy döt Net21:02:03

Good points. Here's a way I have done the equivalent of a cond-lets with or: https://github.com/yaml/yamlscript/blob/main/core/src/yamlscript/transformers.clj#L70-L89 Also I love how shadowing allows me to use any symbol I want in a local context! Very refreshing compared to other langs and their keywords. Although I guess that's just Lisp at work...

p-himik22:02:44

> Here's a way I have done the equivalent of a cond-lets with or The first two usages of when-lets don't even have to be when-lets - a plain (when (and ...) ...) and (let [...] (when ...)) will work just as well. The third usage is just a nested when-let - there's no interleaving with other constructs and it's just a single nesting level, so I'd myself would definitely go that route. The fourth usage is a plain when - you don't even need let there. Things like if and when already have ambiguous semantics, as multiple discussions about whether it's OK to have a "else"-less if show (it's totally not OK, it's a cardinal sin, angry goblins will come for you at night). Now imagine the holy wars with things like if-lets, when-lets, cond-lets, etc. where the semantics is even less clear and, as multiple implementations show, different people have vastly different preferences. I myself, after having extensively experimented with such things at some point, now have a very strong preference towards simplicity and relying only on the built-in conditionals. If the nesting gets beyond, say, 3 (judged on a case-by-case basis), I might extract some things into their own functions and that's it. Zero brain cycles used, only the spinal cord was busy. Another unrelated note in hopes that it might be useful - I would definitely suggest not writing stuff like (get-in rhs [0 :Sym]) everywhere. It heavily ties semantics to the structure. If you ever need to change the latter, you're screwed. If someone besides your (or you but in a year) has to read that code, it will be a significant cognitive burden to recreate all that context you have in your head right now for a complete understanding of such structures. Especially when there are things like (or (get-in key [:Sym]) (get-in key [0 :Sym])).

p-himik22:02:23

> I love how shadowing allows me to use any symbol I want in a local context! It's definitely a nice thing to have. It's just that its usage has to be justified. It's very easy to just switch to k/`v` instead of key/`val`. It's insanely hard to debug some absurdly looking errors when you call (key x) and that key ends up being callable, resulting in probably a nil that turns into something else 50 function calls later.

1
Ingy döt Net22:02:41

Thanks for your time on that. I'll take it all into consideration. I never use if w/o an else form, but calva/kondo flag it and I'm allergic to squiggles. I use the when-lets stuff in functions that are transforms from some series of transforms to try. I need to make a group of assertions whilst collecting the important parts that I'll use to construct the result. If any assertion fails along the way, then the function returns nil, indicating that the next one in the series should be called. Some of these assertions produce a part I want to keep and some don't. When they don't I use _ for the binding symbol. The nice thing is that the whole when-lets stays flat whether or not an assertion binding is important. I find it a bit annoying that let things force another level of indentation. One of the things I really like about YS, is that you can mix lets and non-lets with no indentation penalty. This is contrived, but illustrates what I mean:

$ cat eg.ys 
!yamlscript/v0

defn foo():
  a =: 1
  b =: 2
  say: a b
  c =: 3
  say: c a
  say: c b
  d =: 4
  =>: a + b + c + d

$ ys -c eg.ys 
(defn foo []
 (let [a 1
       b 2]
   (say a b)
   (let [c 3]
     (say c a)
     (say c b)
     (let [d 4]
       (_+ a b c d)))))

vemv20:02:50

Anyone ever experienced some sort of thread leak in Quartz as the Integrant/Component system was repeatedly restarted? There's nothing obviously wrong in the setup I'm seeing, there's a stop method which ends up calling (.shutdown (:twarc/quartz this) true)

hiredman20:02:13

to start with integrant and component don't know anything about quartz, so you should be able to reproduce the issue with out then (starting and stopping quartz in a loop). If that reproduces the leak it is somewhere in quartz, if it doesn't it is somewhere in the code you are using that adapts quartz to integrant or component

👍 1