Fork me on GitHub
#clojure
<
2021-07-12
>
Karol Wójcik03:07:31

Anyone ever saw this?

hiredman04:07:24

The big long strings aren't identical

hiredman04:07:26

Ah, yeah, the string is too long to be embedded in bytecode

hiredman04:07:20

In the repl because it is a trivial def, the compiler doesn't bother generating bytecode

hiredman04:07:29

The length limit for strings in bytecode is, uh, either Short/MAX_VALUE or something shorter (I think 16 bit number is yes for strings in dataoutputstream, but the jvm spec may further restrict it, and the library clojure uses to generate bytecode may also restrict it

Karol Wójcik05:07:41

Huh. I didn't know that!

aaron5104:07:53

When publishing an internal clj library with maven, should it be distributed as a jar or an uberjar?

seancorfield05:07:25

@aaron51 Libraries should pretty much always be (thin) JAR files.

👍 2
aaron5118:07:53

@seancorfield Seems like a thin JAR requires the library’s consumer to list the library’s dependencies in the consumer’s own project.clj… how can I not require the library consumer to list those? thanks3

seancorfield18:07:53

@aaron51 No, the library's dependencies would be automatically fetched by the the CLI / lein etc, as part of dependency management. That is automatic.

seancorfield05:07:05

An uberjar is generally an application, not a dependency that others would use.

Jim Newton10:07:24

is there already a macro which lets me mix let and letfn without excessive indentation? Or do I need to write this myself. not very difficult of course

vemv11:07:00

there is! https://github.com/reducecombine/with I never bothered promoting it much - I have enough on my plate with other initiatives. But the principle seems sound and simple to me.

aaron5116:07:04

@U45T93RA6 amazing that this is only 10 lines!

clj 2
Ed10:07:11

I tend to just use (let [f (fn [_] ...)] ...) . I don't tend to find letfn any more readable. I'm not aware of anything that does that. But like you say, it's probably not too hard to write 😉

2
delaguardo11:07:06

the difference between let and letfn is bigger than syntax

(let [f (fn [counter] 
          (when (< counter 10) (f (inc counter))))]
  (f 0))
this code ^ is broken because f in function’s body is unresolved. if you switch to use letfn - it will be bounded
(letfn [(f [counter]
          (when (< counter 10) (f (inc counter))))]
  (f 0))
so letfn is useful if you want mutual recursion

2
👍 2
Ed11:07:31

if you need to recurse you can write

(let [f (fn f [counter] 
            (when (< counter 10) (f (inc counter))))]
    (f 0))

Ed11:07:40

it's only when you have 2 functions that call each other you need to use letfn ...

delaguardo11:07:08

I just wanted to point out that there is a significant difference between let and letfn beside readability)

2
Ed11:07:14

and I guess when things get that complicated I tend to use defn

Ed12:07:29

fair enough ... guess that I went for readability because the op mentioned indenting rather than anything else 😉

Jim Newton15:07:53

recurse? do you mean recure?

Jim Newton15:07:06

def: recure: do do something recursively.

Jim Newton15:07:23

recurse: to say a naughty word multiple times.

Ed15:07:50

I mean turn someone into a newt for the second time 😜

Jim Newton15:07:10

yes, then that is correct.

Jim Newton10:07:48

yes I also use (let [f (fn ...

restenb11:07:58

anybody know why ring considers :content-typein request maps to be deprecated?

delaguardo11:07:54

probably because the same (or worst - different information) could be presented in “content-type” header

delaguardo11:07:06

the difference between let and letfn is bigger than syntax

(let [f (fn [counter] 
          (when (< counter 10) (f (inc counter))))]
  (f 0))
this code ^ is broken because f in function’s body is unresolved. if you switch to use letfn - it will be bounded
(letfn [(f [counter]
          (when (< counter 10) (f (inc counter))))]
  (f 0))
so letfn is useful if you want mutual recursion

2
👍 2
ChillPillzKillzBillz13:07:22

Say I have a map {:id value}... I need to add to this map where the text for key and the value are dynamically generated... so how do I add to this map such that {:id value :<dyn key> <value} ?

p-himik13:07:16

(assoc {...} key value)

p-himik13:07:55

Or do you mean that you have a string that you need to turn into a keyword? Then keyword. If you have a map literal right there, you can just {:a 1, (keyword "b") 2}.

ChillPillzKillzBillz13:07:23

oh yeah this'll work too. Thanks much!!

p-himik13:07:43

And this is definitely a #beginners material. ;)

ChillPillzKillzBillz13:07:59

I realise that now

😒 2
manutter5113:07:19

I have a Linux server on a mixed Linux/Windows internal network. A remote client is sending me a UNC path like "\\\\10.50.90.18\\ITS Tool\\xml\\foo.xml", and I want to read the file at that address. Do I need something like jCIFS (https://jcifs.org), or should I just bug our sysadmin to set up an smbmount volume on the server?

p-himik13:07:24

If you know for sure that that path can be accessed with SMB, then the answer is "depends". By using SMB from your code, you have (or should have - I don't know the specifics of the protocol and its implementations) the ability to time out over a request. Even local networks are not 100% reliable given that a server can simply be rebooted or misconfigured. But at the same time, using SMB from your code would tie you to that particular protocol. By using an SMB volume, it's the opposite - you no longer have any control over how long you should wait for something to happen. I have seen quite a few times when a program just hangs there for hours when the share suddenly becomes unavailable mid-transfer. But as an upside, you don't depend on a protocol anymore.

p-himik13:07:57

And if you don't know whether it's SMB, then definitely bug the admin to at least figure out what should be used. :)

manutter5114:07:53

That’s valuable information, thanks!

Clément Ronzon15:07:59

Hi gals and guys, I have a question about protocols and records. I defined several records based on a protocol like so:

(defprotocol PaymentFrequency
  "Protocol for payment frequencies"
  (^String toString [_] "Returns a human-friendly string describing the payment frequency")
  (next-payment-frequency [_this options] "Returns a payment frequency set to next start date"))

(defrecord Weekly [^Temporal start-date]
  PaymentFrequency
  (toString [this]
    (obj-to-str this))
  (next-payment-frequency [_this options]
    (-> (-> start-date (jt/plus (-> 7 jt/days)))
        (adjust start-date options)
        (->Weekly))))

(defrecord BiWeekly [^Temporal start-date]
  PaymentFrequency
  (toString [this]
    (obj-to-str this))
  (next-payment-frequency [_this options]
    (-> (-> start-date (jt/plus (-> 14 jt/days)))
        (adjust start-date options)
        (->BiWeekly))))
Is there a way to not to have to write the toString implementation for every defrecord? I tried to do something like this but it won't work:
(defprotocol Stringable
  (^String toString [_] "Returns a human-friendly string describing the payment frequency"))

(extend PaymentFrequency
  Stringable
  {:toString #(obj-to-str %)})
I get this compilation error: java.lang.ClassCastException: class clojure.lang.PersistentArrayMap cannot be cast to class java.lang.Class (clojure.lang.PersistentArrayMap is in unnamed module of loader 'app'; java.lang.Class is in module java.base of loader 'bootstrap') Any idea please?

Russell Mull15:07:12

Some ideas: • Make it a regular function. If it's doing the same thing everything, you don't need protocol dispatch. • If you really need a default, and want to override it sometimes: ◦ Make another protocol for toString ◦ Implement it on Object, as the default ◦ Then implement it on our more specific records as required ◦ Not 100% sure on this, it's been awhile since I've tried something like this.

dpsutton15:07:36

you're extending PaymentFrequency which is a protocol. And that var of PaymentFrequency is a map holding some information (:on :on-interface :sigs :var :method-map :method-builders). That's the tangible error there. But you cannot extend a protocol with another protocol like that.

Clément Ronzon15:07:32

Thank you for the ideas @U7ZL911B3!

Clément Ronzon15:07:40

Thank you for the explanation @U11BV7MTK!

noisesmith15:07:51

also, in case toString is your actual method and not a random placeholder: every class extends Object which has toString, and it's not a good idea to make your own special toString on a different class

Clément Ronzon15:07:03

Yeah, thank you @noisesmith, I think I read that somewhere.

Joshua Suskalo15:07:43

Something else to consider here is to make your to-string into a multimethod that dispatches on type. If you want to have multiple different records be converted to strings in the same way, you can either have a :default value for the multimethod, or you can use clojure's hierarchies.

noisesmith15:07:37

@U01EGMY9273 the fix is easy

(defprotocol PaymentFrequency
  "Protocol for payment frequencies"
  (next-payment-frequency [_this options] "Returns a payment frequency set to next start date"))

(defrecord Weekly [^Temporal start-date]
  Object
  (toString [this]
    (obj-to-str this))
  PaymentFrequency
  (next-payment-frequency [_this options]
    (-> (-> start-date (jt/plus (-> 7 jt/days)))
        (adjust start-date options)
        (->Weekly))))

ghadi15:07:55

why override toString in the first place?

Clément Ronzon15:07:58

@noisesmith the override of toString in my first snippet works as is. I just wanted to not have to write it for every record.

Clément Ronzon15:07:13

@U050ECB92 when you convert those records to string, without the toString override, you get something that is not meaningful: ...ns...Weekly@e2a12542 for instance.

Clément Ronzon15:07:50

With a custom toString I can do things like:

(str (->Weekly (java-time/local-date 2021 7 12)))
=> "Weekly (start-date: 2021-07-12)"

noisesmith15:07:36

right, but you want the toString on Object, not some new method of that name, AFAIK what you have there is only working by accident

Clément Ronzon16:07:02

@noisesmith interesting! But then I don't want to change the Object toString for the reasons we know. Would it help if I use an interface instead of a protocol for PaymentFrequency?

noisesmith16:07:52

"for the reasons we know" - what reasons?

noisesmith16:07:37

the intended usage of toString is that every class would implement it as appropriate, it's meant to be overridden

noisesmith16:07:51

it's a bigger problem to use the same method name and arg list on a different interface

noisesmith16:07:37

@U5NCUG8NR the thing is - it's overkill to define a clojure multimethod or hierarchy here - the jvm already dispatches toString based on class and expects every class to have a definition

noisesmith16:07:48

if two classes want to have the same custom logic, a function is a perfect abstraction for that IMHO

Joshua Suskalo16:07:02

I'm more or less assuming (possibly incorrectly) that the name of the method matching the one on java.lang.Object is coincidence @noisesmith

Clément Ronzon16:07:38

It was intentional actually 😉

noisesmith16:07:07

@U01EGMY9273 you are supposed to extend Object toString, that's what it's for - the problem is adding a toString method with the same arg list on some other interface

emccue16:07:06

(defmacro define-schedule-record [& stuff]
  `(defrecord ~@stuff Object (~'toString [this#] (obj-to-string this#))))

emccue16:07:12

here thee go

emccue16:07:33

if you really don't want to write that out, make a macro

Clément Ronzon16:07:09

thx @U3JH98J4R that sounds like a good option! 🙂

noisesmith16:07:54

@U3JH98J4R btw you don't need the quote/unquote on Object, the resolved version is going to be correct

emccue16:07:12

fixed

🙌 2
emccue16:07:24

@U01EGMY9273 I will stress that you prob. don't want to use toString for a representation intended to be seen by users

emccue16:07:43

print-method and toString are best used just for debugging

Patrick Farwick18:07:50

Does anyone have any experience running Etaoin inside a docker container? Seem to be running into an issue with the default timeout. I see that there are some wrapper functions that should be able to change the default timeout but not quite sure how to get it to work. This is the error

{:type :etaoin/timeout, :message nil, :timeout 7, :interval 0.33, :times 22, :predicate #object[etaoin.api$wait_running$fn__5604 0x6464f017 "etaoin.api$wait_running$fn__5604@6464f017"]}
{:type :etaoin/timeout, :message nil, :timeout 7, :interval 0.33, :times 22, :predicate #object[etaoin.api$wait_running$fn__5604 0x6464f017 "etaoin.api$wait_running$fn__5604@6464f017"]} {:type :etaoin/timeout, :message nil, :timeout 7, :interval 0.33, :times 22, :predicate #object[etaoin.api$wait_running$fn__5604 0x6464f017 "etaoin.api$wait_running$fn__5604@6464f017"]}

Patrick Farwick18:07:02

The chrome portion of it seems to be working okay? Time out happens on both: (def driver (web/chrome)) and (def driver (web/chrome {:headless true}))

Patrick Farwick18:07:19

which maybe means the chrome portion is not actually working okay 🙂

jpmonettas18:07:24

hi everybody, are the sources for the clojure cli tool public? I can't find them on github

p-himik18:07:00

The repo's name is not very descriptive, yeah.

p-himik18:07:52

Ah, master seems outdated. Here's a recent version, at a different path: https://github.com/clojure/brew-install/blob/1.10.3/src/main/resources/clojure/install/clojure

jpmonettas18:07:07

nice! thanks, yeah I got confused by the brew prefix

borkdude19:07:02

In case you're interested in a port to Clojure of that bash code: https://github.com/borkdude/deps.clj

👍 2
zendevil.eth19:07:53

what’s the best practice for running a leiningen project in production: making a jar and running the jar or lein run? Are there pros and cons to each approach?

noisesmith19:07:37

lein is a build tool and best practice is to use lein to make a jar, then use java to run that jar

noisesmith19:07:52

for starters, lein run starts two jvms

noisesmith19:07:27

various factors might make lein run indeterminate for the same code, having a jar is very good for reproducibility and debugging

noisesmith19:07:39

also there's a wealth of existing tooling and industry knowledge about using a jvm in prod, by eliminating the middle man you can leverage it directly

noisesmith19:07:21

as far as I can tell the only argument for using lein on a prod machine is that it's the only way you know how to run clojure code, and that's easily fixed in a five minute hands on session

zendevil.eth19:07:03

Yeah making jars was just taking longer than lein run so I thought why not lein run

noisesmith19:07:46

that's with your m2 cache already filled I assume

noisesmith19:07:25

also, that likely means you had :aot set, you probably don't need :aot

zendevil.eth19:07:59

I’m talking about while creating a docker instance

zendevil.eth19:07:27

what’s the point of :aot :all?

noisesmith19:07:35

OK you make a jar first, then put that in your docker

noisesmith19:07:58

:aot can speed up startup, and allows java code to invoke your namespace as if it were an object

zendevil.eth19:07:05

since I want to run the tests too, wouldn’t it be better to copy the whole source and create the jar in the container?

zendevil.eth19:07:19

and then run the tests with lein test inside the container?

zendevil.eth19:07:23

and also want to run cljs tests

noisesmith20:07:05

you shouldn't even publish the jar if the tests fail • run tests • make a jar • push that jar to a repo • build a docker image using that jar • publish that image

noisesmith20:07:44

building a jar before running tests is probably a waste of resources

dpsutton20:07:58

every message here has extended the complexity: 1) make a jar 2) docker 3) aot 4) testing 5) cljs. Simplify and focus on a single task at a time. Learn how to make a jar. Don't let any other considerations get introduced into that learning until you know how to make a jar

4
zendevil.eth20:07:20

I already know how to make a jar

zendevil.eth20:07:40

I have already deployed the jar on docker and also done it through lein run

zendevil.eth20:07:15

what if the whole point is to run the tests in your container to see if it actually works? @noisesmith

noisesmith20:07:38

that's a separate testing step in that case

noisesmith20:07:09

I mean, if you have infinite CI budget/resources you can combine that tests, but IMHO it's better to have a fail fast before building

noisesmith20:07:10

@ps going back to your initial question - using a jar is currently the undisputed best practice, arguing for using a build tool in prod requires some sort of use case or advantage, and it sounds like you are trying to convince me it's acceptable

2
ahungry20:07:03

I find it convenient to use a docker container as the build environment - sometimes even GNU/Linux package manager provided deps are not reliable/reproducible, I encountered some problems with Ubuntu and dependencies with openjfx - but that'd still look like 1. Build jar (in docker or otherwise), 2. Ship jar/run jar (in docker or otherwise)

zendevil.eth20:07:32

@m131 what about the tests?

ahungry20:07:48

One difference in execution between an (uber)jar produced via lein and using lein run as your entrypoint could be anything you produce during macro expansion at compile time - for example, if you expanded at compile time something that printed the current date as, say, "boot up date of this app" - the uberjar is gonna snapshot it at the time of jar creation - lein run is going to "snapshot" it as the app starts up

zendevil.eth20:07:13

or a separate uberjar?

ahungry20:07:26

(but that'd be a real weird practice) - yes, I would lein test && lein uberjar or w/e on your build env

borkdude20:07:27

I believe lein supports lein do test, uberjar ?

ahungry20:07:46

(that's probably better ^ 🙂 )

borkdude20:07:10

(not sure if that works since test probably pulls in more deps than you would like with the uberjar... never used it like this :)

noisesmith20:07:15

lein test / lein uberjar as separate steps likely gives better error output from your build system

noisesmith20:07:42

@borkdude it works, uberjar doesn't put everything in the jar, just what's resolved as needed for the jar

ghadi20:07:55

+1 using lein to run your application in prod is a bad idea. Lots of additional failure domains, and it's slow

p-himik20:07:45

Regarding reproducibility - assuming the network is perfect and all dependency versions are fixed, is there a way for clj -Srepro -M:run to produce different results in different setups?

noisesmith20:07:49

another advantage of a jar: if there's a failure / odd behavior, I can download the jar and know I'm looking at exactly the code that the vm in prod sees

hiredman20:07:40

this is why we should be deploying merkle trees

noisesmith20:07:54

@p-himik for starters, you could have a different git checkout, or a dirty branch, or a reference to a relative directory with different code checked out

dpsutton20:07:38

and if you have something in the maven cache dir it will be used. i've heard of people installing modified libs and forgetting about it affecting stuff like this

noisesmith20:07:20

right, I've been bitten by that when doing local dev on some lib, and doing a local install to cache without changing the version string

noisesmith20:07:34

(there's a few things there I don't do any more for obvious reasons :D)

p-himik20:07:15

My projects are probably rather niche to draw any robust conclusions from my observations, but in 5 years I have never been bitten by anything like that - in part because the deploys always happen automatically, nobody tinkers with the env on prod servers. So, I had to spend exactly 0 hours on such issues. However, I had to spend multiple hours figuring out: • How to build an uberjar • What tool to use • Why sometimes the tool of my choice produces obscure errors and sometimes it doesn't • Why the resulted jar sometimes just works but sometimes fails with other obscure errors Select a new tool, rinse, repeat. I heard good things about depstar, but by the time I discovered it, I was already exhausted.

noisesmith20:07:49

I didn't even take clj / clojure cli seriously until I found a reliable and simple way to make an uberjar

ghadi20:07:19

I felt the same way, that's why I wrote depstar originally

2
🙏 2
ghadi20:07:27

ship a 🍰 to production, not a recipe for a cake

seancorfield20:07:36

Our process at work is that our CI system builds uberjars (using depstar) if-and-only-if the complete test suite passes, and those uberjar files are automatically deployed to our staging "QA" server for final testing/approval by QA/management, and we have a web UI that lets QA/management release the uberjars to production and that's all automated.

seancorfield20:07:21

Many years ago we used to do source deploys and lein run (and, later, boot run) but, yeah, that's just a bunch of pain you don't need 🙂

seancorfield20:07:56

Since we use depstar as a critical part of our workflow, it gets plenty of love in terms of maintenance 🙂

ghadi20:07:02

my usual CI process: run tests make jar stuff git SHA info into edn file in jar (for health check endpoints / ops-y things) optionally make and push container image built around the jar

dominicm20:07:04

I once saw a macro which grabbed git sha info, so it would pick it up at aot time. I was not very impressed.

seancorfield20:07:12

depstar puts the git SHA into the manifest pom.properties -- revision=... with the output of git rev-parse HEAD and I see tools.build has a git rev count function for versioning the project.

😮 2
ghadi20:07:35

GIT_SHA=$(git rev-parse HEAD)
cat > artifact/service.meta.edn <<EOF
   {:git/sha "$GIT_SHA"}
EOF
^ I used to do that then do jar -u to update the artifact

ghadi20:07:49

then respond with the meta payload on the healthchecks

seancorfield20:07:13

At work we have a -X wrapper for depstar which puts a git SHA/tag combo into a generated (ns ws.uberjar.release) as a version def. And all our apps know they can get the version from there to report in health checks etc.

seancorfield20:07:45

(I was slightly tempted to add something similar to depstar itself but I resisted!)

borkdude20:07:55

I often put a file under resources for this so I can use io/resource to get the version

6
hiredman20:07:34

git describe --tags is a nice alternative to raw shas, particular if you tag releases often with date based tags

2
seancorfield21:07:59

(I think that's what we use at work, right? I don't have that open right now since I'm on vacation 🙂 )

Tiago Dall'Oca21:07:33

Hi! I'd like to know if I could use Datascript "reactively" linked to a Postgres DB. I explain it in more details https://www.reddit.com/r/Clojure/comments/oiz3vb/datascript_automatic_persistency/. To sum up, changes get cached in-memory in an instance of a Datascript DB for being persisted later on. Besides, the application should always query the Datascript DB instead of directly reading from Postgres. P.S.: I'm doing an integrator MVP to connect Jira projects to another project management service. This will be my first Clojure project to run in production 🙂 EDIT: It's still in early stages of development, any architecture tips and advices are also welcome

aaron5118:07:53

@seancorfield Seems like a thin JAR requires the library’s consumer to list the library’s dependencies in the consumer’s own project.clj… how can I not require the library consumer to list those? thanks3