Fork me on GitHub
#clojure
<
2020-04-09
>
didibus01:04:02

What do you guys think of this macro:

(defmacro until->
  [pred & [expr & forms]]
  (let [g (gensym)
        steps (map (fn [step]
                     `(if (or (~pred ~g) (instance? Throwable ~g))
                        ~g
                        (try
                          (-> ~g ~step)
                          (catch Throwable t#
                            t#))))
                   forms)]
    `(let [~g (try ~expr (catch Throwable t# t#))
           ~@(interleave (repeat g) (butlast steps))]
       ~(if (empty? steps)
          g
          (last steps)))))

❤️ 4
didibus01:04:45

It'll keep threading until pred is true, at which point it'll short-circuit and return the last value

didibus01:04:18

I also made it so it captures thrown exceptions, and short-circuit on them and return them as a value instead

didibus01:04:08

(defn might-error-or-throw [e]
  (case (rand-nth [:a :b :c])
    :a :error
    :b (throw (ex-info "Failed!" {e e}))
    :c e))

(until-> #{:error} (might-error-or-throw 10) inc might-error-or-throw inc)

seancorfield01:04:30

Until you showed the usage I couldn't imagine how it worked... Hmm... I'm still thinking about it...

didibus02:04:03

Haha, ya, I should always accompany a macro with a usage examples 😛

seancorfield02:04:14

It's sort of take-while but complemented and threaded...?

didibus02:04:37

Kind of, but not for sequences

didibus02:04:58

More like a generic version of some->

seancorfield02:04:06

Ah, right. Of course.

didibus02:04:37

(defn might-be-nil [e]
  (case (rand-nth [:a :b])
    :a nil
    :b e))

(until-> nil? (might-be-nil 10) inc might-be-nil inc)

didibus02:04:13

Maybe it be better to call it: shortcircuit-on-> but that looks a bit ugly 😛

eval-on-point02:04:15

a more generic way might be a threading macro that takes a predicate to decide when to short circuit?

seancorfield02:04:17

I can't remember ever needing this sort of computation but it's interesting.

didibus02:04:33

That's what my macro does! 😉

didibus02:04:00

It short-circtuit when pred is true

didibus02:04:14

Also short-circuit if something throws an exception

eval-on-point02:04:17

ohh right missed that arg

didibus02:04:20

Thought it be a good way for people who like to treat error as values. They could easily build their own value representation of an error, make a pred for it, and then use until-> to guard for those

eval-on-point02:04:16

you might be able to similarly reduce across a list of functions

noisesmith03:04:33

in that case you can just used a reduced as your "error" value

💎 8
charch06:04:52

Are there any neat libraries for test coverage (preferably that are still actively maintained)? In my brief time with Eclipse and Java, I always appreciated being told which branches in code weren't being executed; of course it led to more robust code (or exceedingly painful headaches).

andy.fingerhut06:04:18

I haven't used cloverage myself, but have heard of people using it

hindol08:04:03

@UN3A4KE13 If you are using Kaocha as the test runner, there is a Cloverage plugin. Really easy to use.

❤️ 4
charch15:04:16

Thank you both, that happens to be the perfect solution

charch16:04:56

Is it a Leinengen plugin only? I'm having issues with the project I'm working on which is vanilla clj.

andy.fingerhut16:04:02

The README for Cloverage gives brief instructions for running it without using Leiningen.

didibus21:04:04

I like cloverage, but I don't think it can do diff since last

didibus21:04:15

It's always absolute coverage

Jakub Holý (HolyJak)10:04:13

Hi folks! Is there any good remote pair programming tool that is neither Emacs nor terminal-based?

sogaiu10:04:02

i haven't used it myself, but i've heard people use party-repl (i think there may have been a conj talk too?) -- there was mention of it recently in some channel

sogaiu10:04:14

not sure if it is remote, but i think it uses teletype so may be it is?

Jakub Holý (HolyJak)11:04:15

Thanks a lot! So Far I found VS Code Live Share and https://floobits.com (cross-editor, low price), which both look good. I'll have a look at party REPL.

sogaiu12:04:43

i presume it would have been easy to find, but fwiw, here's a link to a party-repl talk: https://www.youtube.com/watch?v=AJING0Vigpg

👍 4
Jakub Holý (HolyJak)12:04:45

There is also https://www.saros-project.org/documentation/getting-started.html?tab=intellij#restrictions, a mature Eclipse OSS solution for Eclipse with alpha for IntelliJ

sogaiu12:04:41

interesting, had never heard of that -- thanks for sharing :thumbsup:

👍 4
adamfeldman13:04:13

(macOS-only currently) https://tuple.app

👀 4
henrik06:04:21

https://screen.so/ is from the original Screenhero people. I think Tuple is more mature at the moment, but OTOH Screen is multiplayer and costs a grand total of $0 during the crisis.

❤️ 4
Eamonn Sullivan10:04:46

Hi everyone. I'm doing a quick spike today on using the Github API (which I gather is GraphQL) to find repos inside a particular organisation and with particular tags. Is there a good GraphQL client library for Clojure, or does anyone know of a project that does something similar already that I could study? A quick Google search is finding mostly ClojureScript, so maybe I should do that instead...

jumar11:04:51

GitHub also have a more traditional REST API that might be easier to use for you.

Jakub Holý (HolyJak)12:04:11

You can ask in #graphql But underneath it is just a POST? And creating a transformation from Clojure data to a GraphQL query string should be simple I guess...

Jakub Holý (HolyJak)12:04:35

You can also ask in #find-my-lib

4
Avichal13:04:18

Not really a GraphQL client but I wrote this library to generate GraphQL queries. I think for a very similar use case: https://github.com/avichalp/hql

👀 4
aisamu14:04:20

#pathom (with Connect) might do the trick!

👀 4
rshetty11:04:19

Hey folks, I have a overloaded function with different arity like

(defn overload 
  ([a]
   (println a))
   
   ([a b]
    (println a b))

    ([a b c]
      (println a b c)))
So we have seen this argument list growing continuously with more people adding more arguments and so on This is causing the method to grow large. Are there any generic patterns to refactor this method?

kelveden12:04:56

clojure.core has a few functions with many different arities but I think that's more for performance than anything else (I seem to recall a discussion about it a few days ago in fact). You could perhaps use optional parameters (defn overload [& args]) and then use apply something like (apply println args). But then bear in mind https://stuartsierra.com/2015/06/01/clojure-donts-optional-arguments-with-varargs

Jakub Holý (HolyJak)12:04:54

I guess a map of optional arguments is a common solution. Though you might want to look at the bigger picture first - why is this happening? Is something wrong / missing in your design? Can you change it somehow to make the need to add arguments go away?

henrik06:04:31

Does it do many things? Can you take it apart to functions that do fewer things, and compose them?

deas13:04:10

Feel I must be missing something, but here it goes: I wan't a lein/`clj` based docker image with all dependencies baked it. What would one RUNin the build expressing "Just download all deps lein/`clj`" ?

potetm13:04:59

@deas build an uberjar and install it in the docker image

deas13:04:52

@potetm Thought about using an uberjar. Wonder how come there is no "clean" way to say "Just download the deps clj." The functionality is all there already. :thinking_face: Same with lein.

ghadi13:04:22

It is not clear to me what you're asking for @deas

ghadi13:04:40

what does the intended docker image run when given no args, or when given args?

potetm13:04:56

You can do that. I dunno what’s stopping you.

potetm13:04:23

But a single binary is much more reliable in a lot of facets.

💯 4
potetm14:04:53

You can, e.g., run clj --eval '(println "downloaded!")' from the base directory in the project

parrot 8
deas14:04:52

That's what I was missing. Thanks!

potetm14:04:38

It’s possible that something like clj -Spath would work as well. You might wanna check that.

deas14:04:19

Exactly. Felt I was missing something super simple. 😂

potetm14:04:05

that’ll download all deps before running the eval form

potetm14:04:55

(But, again, I recommend against that. I see zero upside to doing that vs building a single binary.)

deas14:04:06

@ghadi @potetm I want a docker image that starts fast. Preferrably based on clojure:tools-deps and just using cljfor simplicity. ADD foo.clj RUN clj -S deps ... --eval '(println "bla")'and thats it.

potetm14:04:03

Your app will start faster with an uberjar.

deas14:04:32

Should be as simple as possible. Guess the uberjar won't matter much compared to the situation where everything is downloaded before the container is instantiated.

potetm14:04:04

Possible, but you will still incur overhead for calling clj as opposed to java directly

potetm14:04:22

And you can build it using tools.deps using depstar.

potetm14:04:05

Having a single, static artifact in prod is, in many ways, simpler.

deas14:04:29

It's really about a quick super simple demo for beginners.

potetm14:04:16

well… I’m very wary about example code that can’t be used in prod

potetm14:04:23

because people often misunderstand

potetm14:04:57

but I do understand why you would want to remove a step for a demo

deas14:04:16

Don't want to bother people with lein. Just run a webserver with a few lines of code.

deas14:04:16

That's it. The shortest possible demo running a ring handler in a docker container.

✔️ 4
potetm14:04:01

Option 1: Ensure certain commands have been previously executed in an environment (i.e. temporal complection) Option 2: Ship the artifact (i.e. data).

ghadi14:04:25

running clj -Sdeps as the CMD portion of your container will probably never use cached classpath -> thus will not startup quickly

ghadi14:04:20

I wrote RUN but I meant CMD ^

potetm14:04:42

@ghadi he said in a thread it’s for an example. He wants to remove steps for clarity.

ghadi14:04:06

(but inside a container build always use clojure not clj because no terminal input)

ghadi14:04:37

I had been lamenting that for years

aw_yeah 4
jdkealy16:04:23

Are there any good libraries / guidelines for managing threads over a load balancer? e.g. if you wanted to split up work, had a redis connection between servers and wanted to split the thread load between nodes. e.g. like what the actor model does in erlang ?

ghadi16:04:45

@jdkealy from the client or server perspective?

jdkealy16:04:41

On a server. Let’s say i had 30k stock symbols i wanted to watch and I had 300 servers. I want to have a thread per stock symbol and have on average 100 threads on each server and a monitor to make sure every thread is still alive.

John MacIsaac16:04:40

How does one insert a newline char into a str concatenation?

; map destructuring
(defn my-location
  [{lat :lat lng :lng}]
  (str "The latitude is: " lat ", and "
       (newline)
       "the longitude is: " lng "."))

(my-location {:lat 22 :lng 81})

; Result I want:
; The latitude is: 22, and
; the longitude is: 81.
;
; Result I get:
; The latitude is: 22, and the longitude is: 81.

noisesmith16:04:40

(newline) prints a newline, \newline is the newline character, and "\n" is a string with the newline character in it

John MacIsaac17:04:55

(defn my-location
  [{lat :lat lng :lng}]
  (str "The latitude is: " lat ", and " 
       (newline)
       \newline
       "\n"
       "the longitude is: " lng "."))

(my-location {:lat 22 :lng 81})
produces a value of: “The latitude is: 22, and \nnthe longitude is: 81.”

John MacIsaac17:04:16

Instead of a value of:

The latitude is: 22, and
the longitude is: 81.

lukasz17:04:56

It's correct as a value, if you want to see the linebreaks you need to println the result

noisesmith17:04:10

@johnmi using both \newline and "\n" is an odd choice

John MacIsaac17:04:36

I was just testing all three at the same time.

John MacIsaac17:04:48

Two problems with using println. 1. println produces a side effect instead of a return value.

noisesmith17:04:07

your complaint was about a behavior - what you saw

noisesmith17:04:10

the string is correct

noisesmith17:04:36

clojure doesn't expand newlines in string literals ever, it escapes them

noisesmith17:04:51

unless you are using a display procedure for side effects

John MacIsaac17:04:02

2. only the second println gets returned a a value.

noisesmith17:04:14

what does that even mean?

John MacIsaac17:04:24

So

(defn my-location
  [{lat :lat lng :lng}]
  (str (println "The latitude is: " lat ", and "
                "the longitude is: " lng ".")))
returns a single line: “The latitude is: 22, and \nthe longitude is: 81.”

lukasz17:04:39

No, it returns a string if you skip the println

noisesmith17:04:41

that returns the string nil

jsn17:04:52

@johnmi perhaps newline doesn't do what you think it does? It prints out and doesn't add anything to your str (it returns null that is skipped by str)

noisesmith17:04:22

I think this conversation belongs in #beginners and there's a misunderstanding of printing to *out* vs. returning values

💯 4
jsn17:04:39

yeah, absolutely

John MacIsaac17:04:54

thanks, I will move it there, if i need to, after learning about “out” vs returning values.

noisesmith17:04:25

this might be a useful template for investigation:

(import ( StringWriter))

(let [o (StringWriter.)]
  (binding [*out* o]
    (println "hi!"))
  (str o))
this replaces the *out* with a temporary io object, then you can separately manipulate your data and the contents of the io object

noisesmith17:04:50

anything inside the binding block will use the string writer, instead of printing to the terminal

noisesmith17:04:04

and the str of a string writer is everything that has been written to it

noisesmith17:04:43

see in that example, the newline from println becomes a \n inside a string, in clojure's preferred data representation

John MacIsaac23:04:13

@U051SS2EU Thank you. I dont understand all of that but will come back to it.

plins17:04:36

so, Im writing a block of code that follows this patterns

(let [f' (f)
      x' (x f')
      ; lots of other function calls
      y' (y)]
  (do-stuff f'  
            x' 
            ; other stuff
            y'))
the problem is that I need to insert log statements which version is more idiomatic ?
(let [...
      users (get-lots-of-users)
      _ (log/info "Found " (count users) " users ")
      ; expect lots of _ (log/...) inside the same let block
     ] ...)
or
(let [...
      users (let [users* (get-lots-of-users)]
              (log/info "Found " (count users*) "users ")
               users*)
           ; expect lots of those nested lets inside the same let block
     ] ...)

pithyless17:04:05

I personally use the _ (..) approach, and feel it is more readable than nested lets. Also, if you're writing a large imperative block of code with multiple exit-points / side-effects / etc., you may be interested in the better-cond macro: https://github.com/Engelberg/better-cond

emccue18:04:55

I use the first one pretty consistently

lukasz17:04:06

could (log/spy :info ....)` be helpful?

👍 4
nick18:04:26

This is so cool. Thanks for sharing!

ataggart18:04:01

There's also spyf if you want to tailor the formatting.

🔥 4
manutter5117:04:05

I'm a lot more likely to write the first version than the second, but usually only if I'm debugging. If it were code I wanted to keep, I'd probably move (log/info "Found " (count users) " users ") inside the get-lots-of-users fn itself.

Alex Miller (Clojure team)18:04:11

I would usually do the former for expediency, or often try to move the calculation out into a function, which does the logging there

Alex Miller (Clojure team)18:04:30

the latter is much more satisfying as a long-term solution

ataggart18:04:19

Aside: using clojure.tools.logging.readable will likely yield better output when logging a mix of literal text and programmatic values.

plins18:04:05

thanks everybody for the input, I think Ill move the logging to the inner functions called inside the let block 🙂

deas20:04:56

Has anyone come up with a poc/bare bones kubernetes operator one can borrow? Asking for a friend. 😉

adamfeldman20:04:30

Are you looking for something usable with Clojure? There are a few (mostly Go) frameworks out there for building k8s operators. Among others: https://github.com/kubernetes-sigs/kubebuilder, https://github.com/operator-framework.

deas20:04:55

Clojure, sure!

adamfeldman20:04:28

Hunted for a moment and found https://github.com/brosenan/lambda-kube. This is also relatively new and related: https://github.com/nubank/clj-kubernetes-api

👍 8
deas20:04:43

Appears this is green field. Colleague will be trying to show off doing it in go. Wondering whether I should dare and try doing the same in Clojure. Appears the fabric8 kubernetes client (used by quarkus) is where one should start.

rutledgepaulv21:04:02

There's a newer k8s client by nubank that follows a similar pattern to the cognitect aws-api. https://github.com/nubank/k8s-api

👍 4
emccue22:04:10

just use the java library

dpsutton21:04:08

(get {(Integer/parseInt "0") :found} (long 0)) is confusing me the more i think about it. But I think I'm really glad it works the way it does

noisesmith21:04:24

because int vs. long?

dpsutton21:04:45

right. i kinda expected the lookup to miss

dpsutton21:04:56

i'm wondering now what ties them together

noisesmith21:04:35

((ins)user=> ((juxt (partial apply =) (partial map hash) (partial map type)) [(byte 42) (short 42) (int 42) (long 42) (bigint 42)])
[true  1871679806 1871679806 1871679806 1871679806) (java.lang.Byte java.lang.Short java.lang.Integer java.lang.Long clojure.lang.BigInt)]
• more informative comparison

noisesmith21:04:49

clojure ensures that things that are = hash to the same value

dpsutton21:04:51

ah, i tried hashing but i did 3 against 4 accidentally

andy.fingerhut21:04:27

Yeah, Clojure does not use Java's default hashCode and equals methods for comparing integer/long/byte/short/biginteger types to each other, instead using clojure.core/= and clojure.core/hash, which do extra work to enable those to behave more like math does.

andy.fingerhut21:04:26

This has a run-time cost over Java's methods, but I don't know off hand of existing benchmark results to quantify that precisely.

andy.fingerhut21:04:27

If you are interested in the 'what they do' for = and hash, this long-ish article summarizes the behavior at the beginning, before diving into more details: https://clojure.org/guides/equality

andy.fingerhut21:04:45

If you are interested in how, well I can point you at Clojure and Java implementation code that does it, but not sure whether that interests you.