Fork me on GitHub
Alan Thompson01:10:30

Does anybody here use `bat-test` from Metosin?  I'm trying to figure out how to stop the capture of unit test output.


It is quite likely no one is using it. The output capture is implemented by eftest: But I don't remember how those options are passed in with bat-test, or if there is a support. Looks like bat-test impl passes the whole opts map to eftest, with some changes, but e.g. from boot task there probably isn't way to add extra options properties.


Kind of surprised to see Clojure beat Go at making 500 concurrent http get requests:


I'm kinda surprised just allows 500 x n fast as possible requests. Is it returning 200 response codes every time? Not sure how/if error responses would effect performance though


Are you sure that keepalive might not come into play in this case. It seems to be enabled by default for http-kit


But also seems to be the case for Go.

Alex Miller (Clojure team)13:10:55 is static files served on cloudfront so you're at the whim of whatever edge cache you're hitting. why http and not https? former should be redirecting to the latter.

Alex Miller (Clojure team)13:10:21

also keep in mind that we're paying for each of those requests :)

😝 1
amalantony13:10:03 is perhaps a better endpoint to benchmark with.


Yeah I couldn't come up with a wording for "it might not be the best etiquette" that captured the magnitude of the etiquette violation I wanted to convey


Sorry @U064X3EF3 I should switch it to and make El Goog pay instead haha

😆 1
Alex Miller (Clojure team)15:10:11

I mean, it's pennies, but maybe :)

didibus15:10:21 Clojure gets 451ms vs 1.864s on GO


Tried to set Go to use HTTP/1.1 only as well, since it was also using HTTP/2 and http-kit doesn't, but I get similar results either way.


@U0K064KQV Sounds like a good story for Hacker News.


Haha, I'm scared of the comment section of such a thing 😛

Nom Nom Mousse08:10:55

Will a (reset! my-atom myval) continue until successful? Or might it stop trying after a while?


Unless I missed sth more obvious, it looks like atoms ultimately delegate to (via AtomicReference) not sure of how to jump from Java code to C code :)


reset! is a volatile variable assignment, there's no way it can fail


I don't think volatile == CAS, for one thing they're different concepts/mechanisms in Java else clojure's Atom would look a lot more like clojure's own volatile!

Nom Nom Mousse08:10:41

I'm using reset! from a child process to send the result back to the main process so the main process can handle the result. I want to be sure the result gets handled eventually.


reset! doesn't use CAS


@U0232JK38BZ you can be damn sure reset! will eventually succeed, unless perhaps you intentionally place a scenario of extreme contention I thought this was more of a 'fun' theoretical question

🙏 1

@U053XQP4S you're right, I was chasing the path for swap! not reset! (the question made me think of retries which brought swap! to mind)

👍 1
Nom Nom Mousse08:10:59

But will every reset! be handled by the watcher that watches the atom that is reset?


is there a way to specify List<String>as type hint?


The <String> part does not exist in runtime or during compilation. Just List should be enough.


How do people like to determine when a function should return nil as opposed to an empty coll ? TMK it’s about choosing a return type and remaining consistent about it. Perhaps there is more to it though? :thinking_face:


In my experience, nil and an empty collection are usually the same, semantically speaking, so I don't bother with consistency in this particular case.


Meh, I worded that poorly... I meant they're semantically the same as output types, not in every possible scenario. As in, should it matter whether filter with no matches gave back nil vs an empty collection? I don't think so


false/nil is like -1 to me [] is like 0 to me everything else is like a positive integer to me


Ι don't get the analogy. Can you elaborate?


It seems to me add-libs still does not exist in tools.deps.alpha ? How can I use it?


Yes. Will it the add-lib3 branch ever be merged?


Until it is you can use the branch as a git dependency


I have an add-libs in my deps.edn too, it works fine, just just have to require in the namespace


then you can use it


note 'tho, the note at the bottom about using a dynamic class loader.


Not sure how the “if we’re starting other processes via aliases ;; such as a socket REPL or Cognitect’s REBL etc” actually means.


Yes. Will it the add-lib3 branch ever be merged?


The core issue remaining is related to the dynamic classloader you need to make it work correct?

Alex Miller (Clojure team)14:10:36

there are several open questions

👍 2

Btw I'm using add-lib3 branch all the time. Very useful!

Alex Miller (Clojure team)15:10:49

do you use it just to try stuff, or do you later add those libs to your deps.edn?


Both, i.e. there are times when I try some lib to see if it works for me, and if it is, I'll add it to deps.edn; and there are times when it's for a throwaway code that gets deleted


ditto! Very useful for trying things out, then if the experiment works, to incorporate it into my deps.edn


Actually, I usually add lib to deps.edn and then call add-libs with that lib, so it's both available at the repl without restart and cursive indexes it to provide autocomplete

Alex Miller (Clojure team)15:10:35

one thing we've wondered about are ratio of "temp experiment" to "know you need" and for the latter, whether it would be useful to have • runtime libs -> persist to deps.edn or • sync runtime from deps.edn (so you'd modify deps.edn, then sync) options instead

Alex Miller (Clojure team)15:10:16

lots of thorny questions in both cases but just thinking about workflows


That's a great insight into the thoughts around the situation 🙂 Thanks for sharing.

Ben Sless16:10:06

Don't ever quote me crediting CL, but the ability to interactively "freeze" or image those dependencies could be useful, i.e. the relationship between deps.edn and the runtime can be bidirectional

Alex Miller (Clojure team)16:10:20

this is generally, very hard to represent. any particular classpath for a project is created from a set of root deps, optionally some aliases that modify that, and then many transitive deps. the versions selected are dependent on that set of deps at classpath calculation time. adding new libs at runtime as new top-level deps is a best effort exercise (you can't "upgrade" or change a version of something you've already included, and you are adding that new dep and its transitive deps in the context of a set of "pinned" versions for all transitive deps. there are lots of ways to get different answers than if you'd started fresh and/or invalid combinations.

Alex Miller (Clojure team)16:10:00

that sloppiness is probably ok if you mostly understand your deps tree and and how this can go wrong (if you add new deps enough times, you will eventually fall over), but that is hard to communicate to a broad range of users. you also want to avoid leading people into this as a way to start an app, so it should be a "repl-only" thing

👍 2
Ben Sless16:10:16

wait, I still didn't get to the point where you replace git with a series of edits in a graph database 🙂

Ben Sless16:10:54

But enough spoilers for the next blog post 😛

Alex Miller (Clojure team)16:10:53

maybe we should call the function try-lib instead or something to indicate it's potential for failure :)

Yehonathan Sharvit16:10:41

Hello macro friends! I'd like to write a macro called go-inst that works like a go macro with a tweak: calls to <! >! and alt! and alts! need to be wrapped in a piece of code. For instance:

(go-inst {:context "my-context"}
         (let [c (chan)] 
           (>! c :data)
           (<! (chan))))
Should be macro expanded to:
  (let ([c (chan)] 
          (println "entering my-context")
          (let [val (>! c :data)]
            (println "leaving my-context")
          (println "entering my-context")
          (let [val (<! c)]
            (println "leaving my-context")

Yehonathan Sharvit16:10:44

I need also to support nested go-inst with different contexts! My question is: what's the simplest way to write the code for this macro? Should I use clojure.walk or Why?


I would think just a normal recursive function, since that would make it easy to control your context.


I would use along with following as the zipper:

(defn clj-zip [obj]
  (z/zipper #(and (seqable? %)
                  (not (string? %)))
            (fn [node children]
              (let [children (remove #{::delete} children)]
                (if (map-entry? node)
                  (vec children)
                  (into (empty node) children))))
you can then walk the tree with something like:
(loop [zip (clj-zip code)
       ctx {}]
  (if (z/end? zip)
    (z/root zip)
    (recur (if (something? ctx (z/node zip)
             (-> (z/edit zip f)
             (z/next zip))
It might be overkill for your example, but it's a general approach that has worked for me in the past.

Yehonathan Sharvit17:10:46

Why zip is better than walk for my use case?


hmmm, it might not be. If it's likely that you'll extend it in the near future, then it might


I never thought of using zip for that. But I guess it lets you go up/down, left/right the tree. I might have to try it on a macro in the future


If you need to accumulate context as you walk up and down the tree, then it's easier with zippers


I've seen people do it with prewalk/postwalk and atoms, but I prefer the zipper approach


I've always used walk normally.


Right, but then you can't use information from parent nodes, only child branches. For analyzing code, the context is usually provided by the parent nodes.

Yehonathan Sharvit18:10:46

In my case I definitely need the context


Can you elaborate? Seems like you just need to know the {:context ...} map, which is a different kind of context - you will know it just because go-inst is something you control. I'm 95% certain regular walk will work wonderfully in your case given that you seemingly need to just replace a particular form with a wrapped version of the same form.

Yehonathan Sharvit19:10:06

With walk, I'll need to find a way not to wrap with both contexts the <! of the inner go-inst. With zip it seems that it will work like this with no effort

Yehonathan Sharvit19:10:12

But I might be wrong


That's only a problem with prewalk, but you can just use clojure.walk/postwalk (assuming I understand your comment correctly).

Yehonathan Sharvit01:10:01

If I use postwalk I won't be able to differentiate between forms that were part of go-inst block and rewritten as a go block and forms that were orginally part of of go block


Not sure I follow. I might need an example. Wouldn't the following work?

(defn go-inst* [ctx body]
  (clojure.walk/postwalk (fn [form]
                           (if (and (list? form)
                                    (#{'<! '>!} (first form)))
                                (println ~(str "entering " (:context ctx)))
                                (let [val# (~(first form) ~@(rest form))]
                                  (println ~(str "leaving " (:context ctx)))

(defmacro go-inst [ctx & body]
     ~@(go-inst* ctx body)))

Yehonathan Sharvit08:10:13

Let me clarfify what I mean with an example:

(go-inst {:context "my-context"}
                                (let [c (chan)] 
                                  (>! c :data)
                                  (<! (chan)))
                               (go-inst {:context "your-context"}
                                (let [c (chan)] 
                                  (>! c :data)
                                  (<! (chan)))) )

Yehonathan Sharvit08:10:31

Is macro expanded to:

  [c (chan)]
   (clojure.core/println "entering my-context")
    [val__12648__auto__ (>! c :data)]
    (clojure.core/println "leaving my-context")
   (clojure.core/println "entering my-context")
    [val__12648__auto__ (<! (chan))]
    (clojure.core/println "leaving my-context")
  {:context "your-context"}
   [c (chan)]
    (clojure.core/println "entering my-context")
     [val__12648__auto__ (>! c :data)]
     (clojure.core/println "leaving my-context")
    (clojure.core/println "entering my-context")
     [val__12648__auto__ (<! (chan))]
     (clojure.core/println "leaving my-context")

Yehonathan Sharvit08:10:40

While it should be expanded to

    [c (chan)]
      (clojure.core/println "entering my-context")
        [val__12648__auto__ (>! c :data)]
        (clojure.core/println "leaving my-context")
      (clojure.core/println "entering your-context")
        [val__12648__auto__ (<! (chan))]
        (clojure.core/println "leaving your-context")


If you need nesting where inner go-inst completely supersedes the outer one, I think you can just leave any calls to go-inst in your postwalk function as they are.


If you need to be able to replace just some parameters (assuming there can be more than just :context), then you can change the go-inst form in your postwalk function so its argument is (merge outer-params inner-params).

Yehonathan Sharvit09:10:42

How can I leave any calls to `go-inst` in my postwalk function as they are?


Just check that the first item of a list is go-inst and ignore that list.

Yehonathan Sharvit13:10:49

hmmm. I need to think about it

Joshua Suskalo20:10:16

I'd like a sanity check: there's no stdlib function that does this, right?

(defn- update-some
  [m k f & args]
  (if-some [res (apply f (get m k) args)]
    (assoc m k res)
    (dissoc m k)))


Indeed, there's none.