clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
Kuldeep Sengar 2021-06-17T05:00:03.462300Z

hey everyone, What are the common use-cases where we should (should not) use defmacro expanding to a function like below:

(defmacro n-defn [fn-name fn-args fn-body]
  `(defn ~fn-name ~fn-args
     (if (not (check-some-condition?))
       (setup-few-things))
     ~fn-body))

(n-defn conditional-function 
        []
        (println "Did something"))

quoll 2021-06-17T06:00:57.470Z

I’d probably avoid the macro:

(defn fn-checker
  [f]
  (fn [& args]
    (when-not (check-some-condition?)
      (setup-few-things))
    (apply f args)))

(defn normal-function
  []
  (println “Did something”))

(def conditional-function (fn-checker normal-function))

quoll 2021-06-17T06:04:57.474Z

This makes normal-function easier to test in isolation of the setup, and it lets you compose on existing functions. It probably has other advantages that I’m forgetting because it’s 2am and I should be asleep instead of looking at my phone 😉

seancorfield 2021-06-17T06:11:01.474200Z

Please do not cross-post between #beginners and #clojure

🆗 1
Juλian (he/him) 2021-06-17T06:53:27.475400Z

Thanks, I'll have a look at jsoup and hickory

Kuldeep Sengar 2021-06-17T06:58:05.476900Z

thanks @quoll it makes sense. a detailed explanation through your mobile at 2 am 🙌

Helins 2021-06-17T13:01:03.478800Z

Criterium experts (or other benchmarking tool), I would like to benchmark some code that needs some preparation prior to each run but I don't want that preparation to be factored in. What would you recommend? 🙂

Ben Sless 2021-06-17T13:14:33.479Z

What kind of preparation? you need to set up some state or just big complicated inputs?

Ben Sless 2021-06-17T13:18:51.479200Z

Generally, jmh is good at setting up a variety of states and inputs

Helins 2021-06-17T13:18:56.479400Z

Essentially prepare a Java object that cannot be reused. It has to be created anew before each run but that preparation is not relevant to what needs to be measured.

Helins 2021-06-17T13:20:53.479600Z

I have never used JMH but I believe it requires generating a class? In this instance I need something like Criterium where you can measure things dynamically.

Ben Sless 2021-06-17T13:21:03.479800Z

it needs to be recreated before each call or before each benchmarking run?

Ben Sless 2021-06-17T13:21:15.480Z

jmh can be used dynamically as well

Helins 2021-06-17T13:21:46.480200Z

Before each call

Helins 2021-06-17T13:22:47.480400Z

Whereas from what I see Criterium only accepts a function. Unless I am mistaken, it doesn't accept anything around that function.

Ben Sless 2021-06-17T13:24:59.480600Z

I'm not sure jmh lets you do that, either

Ben Sless 2021-06-17T13:25:22.480800Z

what I'd do in this case is measure function+setup then only setup and substract

Ben Sless 2021-06-17T13:25:27.481Z

silly but works

borkdude 2021-06-17T13:25:42.481200Z

exactly. perhaps tufte could also be used, it's a profiling tool where you can bucket things

Helins 2021-06-17T13:32:55.483500Z

Yup I thought about doing (setup + fn) - setup but it sounds really suboptimal and can potentially be inaccurate when setup takes significantly more time than fn.

Helins 2021-06-17T13:35:07.485200Z

I used Tufte in the past and it might serve the purpose since it's more of a "profiler". However I don't know how "good" it is for a task that is closer to "benchmarking" (just one expression). Eg. I expect quite a lot of GC, which Criterium tries to mitigate, I don't think Tufte does that kind of work.

tvaughan 2021-06-17T13:35:43.485500Z

I have a long running server process that needs to execute shell commands. I'm running into the problem where these calls don't return, i.e. https://clojuredocs.org/clojure.java.shell/sh#example-542692d6c026201cdc3270e7. However I can't call shutdown-agents or exit which would terminate the server process. I've tried other approaches (babashka.process, conch, clj-shell-utils) without success. I need to set environment variables too so turtle wouldn't work as-is, for example. What have others done in the same situation? Thanks

borkdude 2021-06-17T13:39:25.486700Z

@tvaughan babashka.process lets you set environment variables with :extra-env, clojure.java.shell has this too (`:env`, sets the entire env). babashka.process doesn't need to spawn a thread for a process (if used correctly), so it would not affect shutdown-agents.

borkdude 2021-06-17T13:40:22.487300Z

E.g. when doing @(babashka.process/process ["ls"] {:inherit true}) , there is no future used.

tvaughan 2021-06-17T13:40:30.487400Z

What does "if used correctly" look like? I wasn't able to get babashka.process to terminate

borkdude 2021-06-17T13:40:48.487600Z

It depends on your use case

borkdude 2021-06-17T13:41:24.487800Z

Feel free to make a repro and I can probably say more about it. It's hard to speak in general terms.

tvaughan 2021-06-17T13:41:26.488Z

A long-running server process that needs to run a command and capture stdout

tvaughan 2021-06-17T13:41:30.488200Z

OK

tvaughan 2021-06-17T13:42:34.488400Z

I'll give inherit a try. I missed that in the docs. Thanks

tvaughan 2021-06-17T13:43:33.488600Z

I also used babashka.process/check instead of a deref

borkdude 2021-06-17T13:44:00.488800Z

With :inherit you're not able to capture the string though. check also does a deref, but throws when the exit value isn't 0

tvaughan 2021-06-17T13:44:53.489Z

Ah ok. That won't work then

borkdude 2021-06-17T13:44:55.489200Z

what you can do is:

(slurp (:out (babashka.process/process ["ls"] {:err :inherit}))
This won't use a future either, and will return the output

borkdude 2021-06-17T13:45:09.489400Z

and this will forward stderr to System/err, so you can see if anything goes wrong

tvaughan 2021-06-17T13:45:24.489600Z

Cool. I'll try that. Thanks

borkdude 2021-06-17T13:45:50.489800Z

E.g.:

user=> (slurp (:out (babashka.process/process ["ls"] {:err :inherit})))
"CHANGELOG.md\nLICENSE\nREADME.md\ndoc\nproject.clj\nresources\nsrc\ntest\n"

tvaughan 2021-06-17T13:49:20.490100Z

So omit check or an explicit deref?

borkdude 2021-06-17T13:49:57.490300Z

yeah, the slurp will block the process until it finishes

tvaughan 2021-06-17T13:50:37.490500Z

Perfect

Ben Sless 2021-06-17T13:52:27.490700Z

Another option is to create a pool of objects to be used in advance then consume it, but you'll need a lot of them

Helins 2021-06-17T14:28:49.490900Z

A huge amount! Funny no one had this issue before.

wombawomba 2021-06-17T14:38:27.493600Z

I'm trying to come up with a (clean) way to join a set of possibly overlapping intervals into an equivalent set of nonoverlapping intervals (e.g. #{[1 2] [3 5] [4 6]} -> #{[1 2] [3 6]}). Any ideas?

emccue 2021-06-17T14:42:11.494500Z

@tvaughan there is always just straight ProcesBuilder

Ben Sless 2021-06-17T14:42:31.494600Z

usually the setup is not one-off. can you describe the case a bit more?

tvaughan 2021-06-17T14:43:14.494800Z

Thanks. That's been my backup plan. I didn't see a way to set environment variables with this, so I've been reluctant to reinvent the wheel if there's a ready made solution out there

borkdude 2021-06-17T14:47:47.495500Z

babashka.process is just a thin layer over processbuilder

borkdude 2021-06-17T14:48:27.495800Z

it also exposes the underlying process as :proc so you can do anything you want without being blocked by a "wrapper"

Helins 2021-06-17T14:55:34.496Z

It is about allowing users to benchmark code written in another Lisp. It also runs on the JVM and the "runner" is written in Clojure. Prior to evaluating the benchmark code, a "context" object must be copied (that's the setup) so that each call is run as-if for the first time.

borkdude 2021-06-17T14:57:17.496200Z

what is the other Lisp? just curious

Helins 2021-06-17T15:02:57.496400Z

Convex Lisp from https://convex.world/ It's a distributed, immutable Lisp closely modelled on Clojure. Still pre-alpha so no big fuzz about it (concept is from the creator of core.matrix 😉 ).

Helins 2021-06-17T15:04:15.496600Z

https://convex.world/documentation/tutorial

Endre Bakken Stovner 2021-06-17T15:06:20.496800Z

The problem is called interval merge in bioinfo at least. Sort values on their start/ends. As long as interval i overlaps with i+1 extend interval i with the end of i+1. If i does not overlap start a new one. Never solved that problem functionally though.

wombawomba 2021-06-17T15:07:56.497Z

yeah that's what I came up with

wombawomba 2021-06-17T15:08:12.497200Z

I'm having a hard time writing it out functionally though

2021-06-17T15:10:19.000100Z

I am sure people have had the issue before, at least in the form of misinterpreting the results of criterium benchmarks :-)

kennytilton 2021-06-17T15:10:44.000900Z

So a client questioning our choice of Clojure just challenged me: "I did some googling. The error messages suck." :rolling_on_the_floor_laughing: Took me ten minutes to fight that one off. Thanks, Clojure!

1
kennytilton 2021-06-18T20:19:43.094800Z

@ben.sless https://www.youtube.com/watch?v=Yt4zQqndLdQ Every annual Clojure survey trashes the stack traces. Java? Excellent choice of bar to clear! 👏 Sadly, the real bar is set by Common Lisp. @p-himik Yes, old Google hits should be vacuumed, agreed. The weird thing? No one asked how I saved the sale. Or congratulated me on doing so. You yobbos never change....

kennytilton 2021-06-21T06:50:25.300800Z

FYI, part of the win was figwheel's error reporting, right down to the line number and code, IIRC. Sweet. Should be the default for Clojure.

👍 1
1
2021-06-17T15:11:02.001500Z

The creation of many objects before the benchmarking runs is the only way I can think of that would not require modifying criterium code

Endre Bakken Stovner 2021-06-17T15:11:48.001700Z

A reduce where you keep track of the current interval you are extending and the list of intervals up until now?

Endre Bakken Stovner 2021-06-17T15:12:09.001900Z

I have little time today, but will write it out tomorrow

Ed 2021-06-17T15:18:33.002100Z

(let [i #{[1 2] [3 5] [4 6]}]
    (->> i
         (sort-by first)
         (reduce (fn [r [a b :as n]]
                   (let [[x y] (last r)]
                     (if (and y (< a y b))
                       (conj (vec (butlast r)) [x b])
                       (conj r n))))
                 [])))
maybe something like that? That'll probably get slow with all the last/`butlast` calls, you could use subvec for that or something instead?

😎 1
tvaughan 2021-06-17T15:24:01.002500Z

This appears to be working now. Thanks for your help @borkdude

borkdude 2021-06-17T15:24:27.002700Z

:thumbsup:

Helins 2021-06-17T15:25:52.002900Z

Naively, I guess modifying Criterium to have such a "preparatory" phase might mess with its base design (the work is does with overhead estimation, GC, ...)

wombawomba 2021-06-17T15:26:11.003200Z

nice thanks 🙂 it's pretty similar to what I came up with:

(defn merge-intervals
  [is]
  (if (empty? is)
    []
    (let [is' (sort-by first is)]
      (reduce (fn [xs [a b]]
                (let [[a' b'] (last xs)]
                  (if (<= (dec a) b')
                    (conj (vec (butlast xs)) [a' (max b b')])
                    (conj xs [a b]))))
              [(first is')]
              (rest is')))))

wombawomba 2021-06-17T15:26:58.003400Z

and yeah, I'd like to find a way to get rid of the (conj (vec (butlast xs)))

wombawomba 2021-06-17T15:29:59.003900Z

...although I suppose it might not matter unless I have lots of intersections (which I don't)

Kyle Ferriter 2021-06-17T15:31:32.004400Z

One person's "error messages suck" is another person's "error messages encourage more robust fault handling"

🥑 1
Ed 2021-06-17T15:32:14.004500Z

(let [i #{[1 2] [3 5] [4 6]}]
    (->> i
         (sort-by first)
         (reduce (fn [r [a b :as n]]
                   (let [c     (dec (count r))
                         [x y] (get r c)]
                     (if (and y (< a y b))
                       (conj (vec (subvec r 0 c)) [x b])
                       (conj r n))))
                 [])))
using subvec instead?

🙏 1
Ben Sless 2021-06-17T15:32:23.004700Z

I know it might be a hot take, but "no they don't"

➕ 6
Ed 2021-06-17T15:34:30.004900Z

how did they feel about using jboss or some other jee type app server? I'm pretty sure I recall the error messages being way worse in Java land.

➕ 1
wombawomba 2021-06-17T15:34:58.005200Z

yeah okay good idea

wombawomba 2021-06-17T15:35:09.005400Z

you can remove the vec call there as well

p-himik 2021-06-17T15:44:35.005700Z

And in particular, Clojure 1.10 has introduced a lot of improvements in this area. Random 5 minute googling might easily give results from older versions.

2021-06-17T16:46:14.006300Z

Well. They are not great, but getting better Also a few libraries that try to improve the situation

2021-06-17T19:05:19.006800Z

If the run times of the thing you want to measure is, for example 1/10 of the time required to set up each one, then your preparation time will be 10x longer than the time measured during Criterium measurements of the code you want to measure.

2021-06-17T19:06:50.007Z

Criterium is very flexible on how long you can spend doing measurements, but by default it spends about 10 seconds running the code you want to measure, as many times as it can run during that time, just for its 'warm up' period where it tries to ensure your JVM code has been JITed, and then 30 or 60 seconds of repeatedly running it after that. It discards the stats from the warmup time and reports based only on the (usually) longer measurement period that happens after taht.

dabrazhe 2021-06-17T20:28:08.011100Z

How can I add calculation on a value of the keyword within this function?

(map (juxt #(get-in % [:SELL :safetyP]) :s-premium :margin ) collection ) 
eg. (* :margin 1000) ?

seancorfield 2021-06-17T20:32:00.012100Z

@dennisa It’s not really clear what you’re asking here. map .. juxt is going to produce a sequence of vectors. What input/output are you talking about here?

seancorfield 2021-06-17T20:33:03.013Z

Perhaps you’re looking for (comp (partial * 1000) :margin) (instead of just :margin)? Or #(* 1000 (:margin %))

✔️ 1
dabrazhe 2021-06-17T20:41:00.014800Z

Indeed, I am looking for #(* 1000 (:margin %)) For some reason my function: ) #(* 1000 :margin %) did not work 🙂

dpsutton 2021-06-17T20:45:09.015400Z

do you understand why it doesn't work? Not clear if that means you caught your mistake or you are still confused

dabrazhe 2021-06-17T21:07:22.016800Z

I think I do now. Strangely, I did not have any issues with using get-in in the same context in juxt

dpsutton 2021-06-17T21:25:45.017900Z

its not the context that is incorrect. (* 1000 :margin <anything here>) will always blow up because you cannot multiple 1000 by the keyword :margin

seancorfield 2021-06-17T21:55:03.018200Z

Not sure where you are in your Clojure journey but perhaps #beginners might be a more helpful channel for questions like this, since folks there have opted in to doing more than just answering Qs — they’re happy to help teach folks about the reasons and alternatives and idioms…

2021-06-17T22:50:47.019500Z

I would like to be able to validate in-flight data in a production service against runtime-generated schemas. I know spec2 can do this, as can malli, and potentially a few other libraries. Is there an established and robust way to do this that's preferred? Is spec2 or malli a good solution despite both being marked as alpha software?

2021-06-17T22:57:17.020300Z

there is always "spec1" as well, which ships with clojure

emccue 2021-06-17T22:58:07.020900Z

schema still exists

2021-06-17T22:59:29.022100Z

in my mind the question is how and and to who these schemas are going to be communicating (assuming they are some sort of interface documentation)

2021-06-17T23:01:27.023300Z

is the audience (human and machine) all going to be able to understand the schemas?

2021-06-17T23:01:51.023800Z

do the tools you want to use understand them (maybe swagger, etc)

2021-06-17T23:03:03.025300Z

The audience is just machines. The intended purpose of this is to detect when in-flight data stops conforming to an existing spec (because the data is provided by external sources which may experience deployments that cause changes). The reason to disprefer spec1 is that it relies so heavily on macros and has little way to produce specs that can be altered at runtime easily.

ghadi 2021-06-17T23:05:19.026100Z

Akita Software is doing something in this space ^

ghadi 2021-06-17T23:06:06.027Z

(I’m not affiliated, just think it’s interesting. There will be a talk at Strange Loop about it)

2021-06-17T23:09:35.028500Z

I'll take a look at that, thanks!

2021-06-17T23:10:53.029500Z

It seems like they may be working on a different part of this problem than I'm dealing with, but I'll research some more.

2021-06-17T23:11:55.030600Z

spec (even spec2), bottoms out on predicates, functions, arbitrary blobs of code. it is an open world, so to deal with new things you'll need to create new code. depending on the data format you might be better off with something like json schema, as the set of things (json values) is closed, so you just mix match and combine those

1
2021-06-17T23:14:42.031100Z

Fair enough.