Fork me on GitHub
#clojure
<
2023-11-19
>
namenu15:11:04

How do I find/enumerate all the inputs that are included in the build? Our backend system automatically triggers deployment when the main branch changes, and I want to reduce unnecessary deploys by making a comparison. I tried to compare uberjars between commits to see if they are the same, but the compiler seems to be behaving non-deterministically. Is there such a thing as a build fingerprint? If not, I'd like to know if any of the inputs used in the build have been updated to determine if I should proceed build or not.

p-himik15:11:09

If your builds are tied to commits, don't you already know all the inputs, just from the source tree?

namenu15:11:25

Of course it is, but we're using monorepo, and :local/root deps are used quite a bit, so I'd like to discover it based on deps.edn.

p-himik15:11:35

How exactly do you produce a build?

namenu04:11:05

We are using GitHub and the push trigger in actions cause builds and deploys the main branch. The repository is Polylith based and uberjar is created by https://github.com/polyfy/polylith/blob/master/examples/doc-example/build.clj script.

seancorfield04:11:13

Polylith can tell you which projects need building so you only have to build what needs to be deployed. Check http://corfield.org - I've blogged about how to do this. I'm on my phone right now.

namenu04:11:38

that's fantastic. I am going to find the post right now

seancorfield04:11:02

If that doesn't give you enough detail, ping me tomorrow when I'm at work and I can give you more details

p-himik04:11:16

It seems that even without relying on Polylith it shouldn't be hard to figure out whether a new uberjar will be different from the old one. AFAICT, that build script just uses the top-level :paths provided in your deps.edn and doesn't copy over any resources. If that's correct, you can simply check whether the stuff in contained in the directories referred to by :paths has changed or not.

👍 1
p-himik04:11:24

It's not 100% accurate of course. You can have some x.clj and never require it - it won't affect the uberjar (if your build script is indeed that script) but you wouldn't be able to tell it. You can also potentially have some macros that do crazy stuff during compilation. Though I can't see how Polylith could possibly help in those scenarios either.

namenu04:11:11

@U04V70XH6 that was helpful enough. Polylith & seancorfield solved my problem again. 🙇

1
seancorfield04:11:56

@U2FRKM4TW paths is only a small part of how Polylith projects are built - there's all the transitive local deps too. Polylith knows which have changed and which haven't.

p-himik04:11:35

Right, good point. It's :paths all the way down. :)

chrisn15:11:41

Where are the specs for the clojure core functions and macros defined?

chrisn15:11:46

No - the core macros all have spec enabled for them - let and friends and this was done by the core team I think. Where I am coming from is that compiling ham-fisted's main api namespace is 1200ms with spec enabled and 84ms with it disabled.

Ben Sless07:11:01

Thinking aloud - sure, macro specs are slow, spec implementations are also slow-ish Can they be made faster?

chrisn14:11:51

Well yes but specifically for macro checking like what is in core.specs.alpha (specifically let and fn destructuring) it is just smarter to move the check into the macro expansion code itself. Spec's implementation could be made faster especially if we want to generate bytecode but it won't be free. The compiler already has a number of passes - the lisp reader which can be significantly faster, the analysis phase and the emit phase. All of these can be quicker.

Ben Sless14:11:54

Don't threaten me with a good time 😁

Ben Sless14:11:43

Even without bytecode generation, I believe spec could be faster, malli is proof of a way lower bound atm

chrisn14:11:53

Right I was thinking honestly the best way to get spec faster is convert it to malli 🙂.

chrisn14:11:18

At least start there. Then you can file issue with ikitommi and they will get addressed and you don't have to do it yourself.

Ben Sless14:11:21

You mean port spec forms to malli?

chrisn14:11:15

yes - on another note here is an implementation of clojure.core/map that is faster in all non-chunking situations especially in higher arities where you are looking at 10x or more - https://github.com/cnuernber/clojure/blob/faster-map/src/clj/clojure/core.clj

Ben Sless14:11:33

> Don't have to do it yourself > _Looks at all my merged MRs to malli Oh well_

chrisn14:11:59

haha - you are a champ for doing the work though.

chrisn14:11:23

Also faster every?, some, and filter (in nonchunking case).

chrisn14:11:59

If you add in chunking to the above map implementations in all arities then it really gets a bit faster but the implementation is a lot longer - https://github.com/cnuernber/ham-fisted/blob/master/src/ham_fisted/lazy_caching.clj#L15

chrisn14:11:54

In higher arities that makes the seq-based map implementation equivalent to the lazy-noncaching implementation in ham-fisted which completely surprised me.

Ben Sless14:11:05

In the malli utils you'll find hidden versions of every and some which unroll their args

chrisn14:11:33

I thought about every? as a macro if the input is a persistent vector.

Ben Sless14:11:40

But I need to investigate how your implementation gets these speedups

chrisn14:11:08

Typehinting direct calls to ISeq so you aren't calling RT/seq, RT.first, RT.next all over the place. This allows hotspot to inline the call.

chrisn14:11:25

simple. Could have been done long ago.

Ben Sless14:11:31

> I thought about every? as a macro if the input is a persistent vector. > That's most of clj-fast

chrisn14:11:52

clj-fast has a lot of good stuff.

🙏 1
Ben Sless14:11:59

Passing through RT is important though for dynamism Although you can get many speedups by specializing earlier, which is sort of what you did

chrisn14:11:34

Right I don't bypass RT - I just call 'seq' exactly once so I am not sacrificing any dynamisim that I can see.

mkvlr16:11:41

what do I need to implement for a custom sequence type that’s clojure.lang.Seqable and clojure.lang.Counted so it can be transit-encoded? Code in 🧵.

mkvlr16:11:20

(deftype TransformedCountedSeq [qseq-result transform-fn]
  clojure.lang.Seqable
  (seq [_] (map transform-fn qseq-result))
  clojure.lang.Counted
  (count [_] (count qseq-result)))

(def out (java.io.ByteArrayOutputStream. 2000))
(def w (cognitect.transit/writer out :json {}))
(cognitect.transit/write w (TransformedCountedSeq. [1 2 3] inc))
(ex-message *e);;=> "java.lang.NullPointerException: Cannot invoke \"com.cognitect.transit.WriteHandler.tag(Object)\" because \"h\" is null"

hiredman16:11:55

Your need to specify a handler for your type, transit doesn't know you want to encode it via transforming it into a seq

hiredman16:11:46

That is not a custom sequence, that is a custom seqable

hiredman16:11:08

A custom seq implements ISeq

mkvlr16:11:05

would it work if I implement ISeq?

p-himik16:11:31

It would work if you implement java.util.List since there's a default writer for that.

hiredman16:11:42

Not sure, depends if transit has a built in handler for ISeq

hiredman16:11:18

It wouldn't surprise me if it didn't though

mkvlr16:11:29

right, saw the java.util.List writer. Does implementing that make sense or does that come with other downsides?

p-himik16:11:02

The downside is that your custom type will be serialized as if it were a plain array. If you want to be able to reconstruct that type, you should go with a custom handler. If not, I'd just implement java.util.List.

mkvlr16:11:42

hmm, thinking I might want to implement clojure.lang.LazySeq this would imply java.util.List , right?

mkvlr16:11:40

I’d like my type to behave like the lazy seq that (map transform-fn qseq-result) returns, only also be counted?

p-himik16:11:02

clojure.lang.LazySeq is a final class. What do you mean by "implementing" it?

mkvlr16:11:33

I see, not what I want then 🙃

p-himik16:11:17

I'd probably create a class that implements/extends exactly the same things as the lazy seq class, plus the counted interface, and then delegates everything but the count to the wrapped instance of lazy seq.

mkvlr16:11:22

I’ll try that, so ISeq, Sequential, List, IPending, IHashEq thanks @U2FRKM4TW!

👍 1
hiredman16:11:06

PersistentList is a counted seq

hiredman16:11:56

And I think transit handles them as Lists (they get read back as ArrayList)

mkvlr16:11:37

@U0NCTKEV8 are you suggesting I can drop my custom type in favor of PersistentList?

chrisn16:11:50

Just implement size and get and everything else works

chrisn16:11:26

Which is how the lazy noncaching version of map works

hiredman16:11:32

Or yes, just use persistentlist

hiredman16:11:19

That is the type that clojure.core/list creates, is the type the reader usually produces for things in parens

hiredman16:11:14

The way to convert a lazy sequence (output of map) to a persistentlist is apply list

p-himik16:11:22

I assume that the desire goes beyond the question and it's not only about Transit but also about having a custom counted lazy seq. In that case, just creating a list isn't sufficient.

hiredman17:11:48

It isn't going to be lazy if you are serializing it

p-himik17:11:14

But it will be if the type is used elsewhere, not just for serialization, as the previous thread by the OP seems to imply. Otherwise, one could just use mapv or sequence + map instead of map.

joshcho22:11:44

What's the best way to get consulting from Clojure experts? I have a startup rn building a DSL for LLMs using Clojure, and would love a (paid) consultant who can answer some questions I have about how Clojure fits with my project (for instance rn, async is an issue). Is there a channel for this?

Ben Sless03:11:26

Probably #C05006WDW or #C06B40HMY

❤️ 1
Ed09:11:47

#C5FBP6A5V also has a link to contact info http://www.clojureconsultants.org/

❤️ 1
joshcho10:11:52

thank you all