Fork me on GitHub
#clojure
<
2022-09-14
>
tstout01:09:26

Are there any clojure applications written by Rich Hickey such that the code can be studied? By application, I mean something higher level than clojure.core and friends. I would find it fascinating to see how he uses his creation to build an application.

Alex Miller (Clojure team)01:09:34

But really Stu used to joke that doing a twitch stream of watching Rich code is him lying in a hammock for several days then typing out the final code. Which is, honestly, not that wrong. He does more work before typing a line of code than any programmer I've ever met, so the code is almost incidental at that point

hiredman01:09:36

https://github.com/richhickey/ has a few little repos as well, some interesting early work connecting clojure to amazons sdb and using rdf with clojure

macrobartfast03:09:29

which includes this line of code: :sustain [0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0]

macrobartfast03:09:54

which is somehow validating of some of the things I end up with.

wevrem15:09:59

@U0KTDCDJL what about Rich’s ant simulation?

macrobartfast03:09:58

If one wants to essentially build what might become microservices later, in a monolith, would one want to use something like Mount? If so, is Mount the popular thing these days?

Ben Sless04:09:37

At the risk of starting another flame war Don't use mount

macrobartfast04:09:38

please send the alternative via passenger pigeon using the cypher.

Ben Sless04:09:10

Carrier pigeon unavailable, please stand by for signal

Ben Sless04:09:18

Until we figure out pure effect handlers 🙂

Ben Sless04:09:24

The main issue with mount is it creates global, mutable contextual state Component is super explicit, straightforward and composable

macrobartfast04:09:42

just like the ingredients for bananas flambé.

pavlosmelissinos05:09:24

How about integrant? Is there a guide or something to help you choose if you're not sure of the pros/cons of each? I mean I have a general idea but I don't feel comfortable recommending one or the other. I've heard that the cost of having configuration as data in integrant is some magic that can make it harder to reason about but I don't really understand why that's the case. As if that weren't enough, there was a thread recently here (don't remember which channel) where some people argued it's better to go with your own solution rather than use any of these libraries. edit: Sorry, not interested in a flame war either, I'm genuinely curious.

Ben Sless05:09:24

that's the reason I prefer component. 0 magic

1
1
seancorfield05:09:07

+100 No Magic. Have the code do exactly what you want. Component is the only solution that satisfies that. Mount is global mutable state. Integrant is magical data.

2
👍 1
seancorfield05:09:31

There are some things where abstractions just aren't worth it.

👍 1
pavlosmelissinos05:09:36

Could you give an example of magical data? I don't think I understand the argument. Integrant is generally straightforward from what I've seen but maybe the systems I've worked on are not big/complex enough?

seancorfield05:09:52

You said it "I've heard that the cost of having configuration as data in integrant is some magic"

seancorfield05:09:47

My feeling is that the multimethods are spread all over the place and I find it very hard to figure out how Integrant-based code actually works (because finding all the parts is hard). I do not find this to be an issue with Component.

👍 2
cjohansen06:09:31

We’re using integrant, and I don’t find this to be a particularly big problem. Sure, it uses multimethods, with the caveats that usually applies, but we keep the number of integrant-things to the necessary minimum, and require all the relevant namespaces from the system spec. Works well enough.

💯 1
Ian Fernandez06:09:36

Closeable-maps

p-himik06:09:09

I also use Integrant and find its "magic" relatively transparent, but I have read its implementation more than once. And keeping an eye on juxt/clip in the meantime.

👍 2
Mark Wardle07:09:37

+1 for integrant because it combines configuration with state management. That means I can have one application deployment using services that are provided by libraries - ie working as a monolith - but then switch at the configuration level and use an alternative version that uses the same service but implemented via an external microservice, potentially not even loading the namespaces of services not being used. And one doesn't need to make the data magical; one can use spec to validate and report back when configuration for a service is invalid.

👍 1
cjohansen07:09:47

We use this exact strategy to deploy our app, and parts of it as a service for a third party. It is very nice.

pavlosmelissinos07:09:32

> but then switch at the configuration level and use an alternative version that uses the same service but implemented via an external microservice You can still achieve the same goal with component I think, you just have to map a configuration setting to a function yourself rather than depend on the tool to do it for you, right? Or is it more nuanced than that?

cjohansen07:09:49

The nice thing about integrant is that the implementation is separated from the system description. So you describe the dependencies with data, and then start/stop each service using multi-methods.

cjohansen07:09:27

So instead of a bunch of scattered ifs and buts, you can just build separate system maps and tell integrant to start them

pavlosmelissinos07:09:36

I do like how integrant uses tagged literals to define dependencies but from what I understand I think you can get a similar behaviour with aero + component. I mean I'm not sure you need to have "scattered ifs and buts".

vemv08:09:23

> If one wants to essentially build what might become microservices later I find Component/Integrant/... fairly orthogonal to this. (I'd still use those) For that 'eventual scalability' Polylith is at the right granularity. As its very name indicates, you start as a monolith, which later can become N things (there are problems that it doesn't solve and that are hard to solve generically anyway. e.g. how do services communicate - sync or async?)

Mark Wardle08:09:28

[in regard to component vs integrant] I'm sure as with most things in tech, anything is possible. The other thing that really abstracts the what from the how is pathom - so this makes composing functionality so much easier - my systems are composed of pathom resolvers and mutations and that composition is defined by integrant (and aero) and would as easily work in a monolith or serviced by independent micoroservices.

Ben Sless09:09:57

The dependency map and configuration in component can be loaded as data and configured with aero. It results in a pretty clean solution

Ben Sless09:09:56

It's an often forgotten function, system-using, which essentially lets you provide your dependencies as data

👍 1
Ed10:09:51

Just out of interest, what do people do for turning these component descriptions into infrastructure descriptions? Does anyone have some code in their project build process that takes the dependency map and outputs some terraform or cloudformation or whatever? I ask because it's a thing I've done in a couple of projects now and wondered if anyone else thought this was useful... or is it just me?

Ian Fernandez18:09:10

I find more useful to manage the stratup without making "components" dependant on each other, and Closeable Maps just re-use something that already does the start/stop job that is java.lang.AutoCloseable

macrobartfast03:09:03

I’m drawn to microservices because the encapsulation is easier for me to reason about.

Rupert (All Street)09:09:55

@U0X9N9ZK5. Without further understanding of your goals, to me it would seem a bit overkill to use microservices for the purposes of encapsulation and reasoning about a system? It seems Clojure Namespaces and Libraries also offer encapsulation without the downside of having to spin up microservices and communicate over network. Also, compared to library/namespaces, each microservice call has additional concerns: • Additional latency • Bandwidth limits • Serialisation • Deserialisation • Failure of network or remote machine • Timeouts • Retries Generally, I think Microservices are needed when: • Zero code sharing between services is needed or allowed. • Services need to be managed or owned or deployed independently • Services need to scale independently or require different hardware (e.g. some services require GPUs or lots of memory). • Services could compete for resources or conflict if they are together. • Services have conflicting library requirements (e.g. different versions of the same library). • Services have different stability (e.g. one service needs to be restarted frequently). • The developer wants to learn about microservices or experiment with them.

macrobartfast17:09:22

This is really helpful, and what you’re saying is my sense of what is right. So I need to work on my ‘library and namespace fu’. I think the problem is that I tend to end up pulling a library into a number of different places and forget of where they are (i.e. spaghetti code) where I am dreaming this can be mitigated with, say, microservices and kafka. But then I introduce new issues (and probably some of the same ones).

👍 1
macrobartfast03:09:14

But they introduce their own problems.

macrobartfast03:09:10

Watching the Language of the System talk for insight on all this.

dazld10:09:18

((fnil + 0 0 0) nil nil nil)
=> 0
((fnil + 0 0 0) nil nil)
=> 0
((fnil + 0 0 0) nil)
Execution error (ArityException) at user/eval61095 (form-init17216256328901138196.clj:1).
Wrong number of args (1) passed to: clojure.core/fnil/fn--6971
little puzzled by this behaviour - why does the arity-1 call throw?

1
Martin Půda10:09:51

Look at the (source fnil) - function returned with (fnil + 0 0 0) has arity 2, 3 and 3+.

dazld10:09:19

understood.

dazld10:09:04

kind of a shame - given the backing function has arity 1+ it feels a little inconsistent.

dazld10:09:21

probably dumb, but I don’t get why not add support for arity-1 to fnil? ie:

(defn fnil
  ([f x]
   (fn
     ([a] (f (if (nil? a) x a)))
     ([a b] (f (if (nil? a) x a) b))
     ([a b c] (f (if (nil? a) x a) b c))
     ([a b c & ds] (apply f (if (nil? a) x a) b c ds))))
  ([f x y]
   (fn
     ;; add this
     ([a] (f (if (nil? a) x a)))
     ([a b] (f (if (nil? a) x a) (if (nil? b) y b)))
     ([a b c] (f (if (nil? a) x a) (if (nil? b) y b) c))
     ([a b c & ds] (apply f (if (nil? a) x a) (if (nil? b) y b) c ds))))
  ([f x y z]
   (fn
     ;; add this
     ([a] (f (if (nil? a) x a)))
     ([a b] (f (if (nil? a) x a) (if (nil? b) y b)))
     ([a b c] (f (if (nil? a) x a) (if (nil? b) y b) (if (nil? c) z c)))
     ([a b c & ds] (apply f (if (nil? a) x a) (if (nil? b) y b) (if (nil? c) z c) ds)))))

Jordan Robinson10:09:47

Hey all, was wondering if anyone had experience running a mirror to cache jars for a ci process and had any recommendations. Is it e.g. possible to run clojars on your own network and point lein/clj at that instead? Would you have to use artifactory or something instead and if so, how do you point it to pull from clojars? I had a bit of a search around but it didn't seem like there was much documentation on stuff like this

vemv10:09:32

.m2 caching can work very nicely when set up properly. Have you exhausted that option?

Jordan Robinson11:09:32

in our current setup we spin up a docker worker to build our artifact, so it's not the ideal but it is something I've looked at as an option

Jordan Robinson11:09:11

for additional context there's other artifacts we use (npm, ruby stuff, etc) that we probably are going to want to supply in this way as well, so it would be nicer to have a consistent approach

Ed11:09:24

I've run both artifactory and nexus internally for this. Nexus is easier to get up and running. But you can also have a hosted artifactory pretty easily but I think you will need to pay them for it.

👍 1
mpenet11:09:38

yeah a shared .m2 is a good way to proceed

mpenet11:09:46

even one per worker is not bad

mpenet11:09:02

if you are using gh actions it's quite easy to cache

mpenet11:09:48

I have also heard good things about reposilite

Jordan Robinson11:09:56

that's definitely food for thought, sadly we're not using gh actions so it's not as simple here, but if shared .m2 is a good way to proceed I'll have a look into that, running nexus on our network and reposilite which I hadn't heard of before now

Jordan Robinson11:09:46

reposilite in particular looks really cool

mpenet11:09:28

I didn't try it myself, but know people who did. It's quite young so there's that, I suspect nexus/archiva & co are more solid but also more complex for sure (I have experience with archiva)

Jordan Robinson11:09:00

yeah, one thing that stands out is that it looks like running as a docker container is more of a first class citizen than e.g. nexus/artifactory where it's retrofitted. With our current architecture, running a docker image on the network is exponentially easier than setting up anything else so definitely appealing

mpenet11:09:17

makes sense

Ed11:09:06

if you're going to use npm in the future, it's worth using something that supports that because npm doesn't have a very good track record of providing immutable artifacts that support a repeatable build process ... which may or may not be something that's important to you 😉

👍 1
Ed11:09:30

I hadn't heard of reposilite either ... good find @U050SC7SV 👍

Jeffrey Bay14:09:37

hey all - trying to use clj-goes-fast/clj-async-profiler 1.0.0 and running into a problem; we recently upgraded from java 8 to 17, which necessitated upgrading from 0.51 to 1.0.0, and after solving a few other problems, now I'm stuck on this one:

Caused by: java.lang.IncompatibleClassChangeError: Method 'java.util.Comparator java.util.Map$Entry.comparingByKey()' must be InterfaceMethodref constant
	at clj_async_profiler.post_processing$raw_profile__GT_compact_profile.invokeStatic(post_processing.clj:72)
	at clj_async_profiler.post_processing$raw_profile__GT_compact_profile.invoke(post_processing.clj:55)
	at clj_async_profiler.post_processing$read_raw_profile_file_to_compact_profile.invokeStatic(post_processing.clj:95)
	at clj_async_profiler.post_processing$read_raw_profile_file_to_compact_profile.invoke(post_processing.clj:92)
and curious if anyone has seen it before or has a good suggestion on how to proceed? I tried googling IncompatibleClassChangeError and clj_async_profile, no dice. I also tried poking around in the jar file, it appears the Helpers class (the only class file in the jar) was compiled with java 8... maybe that's a clue? we were able to run all of our stuff compiled with java 8 running on 17 though, so that seems like it should be fine.

hiredman16:09:46

do you do any aot compilation? maybe make sure you don't have any potentially stale class files laying around

John Brown19:09:31

Hi, I am a Java developer with 2 years of experience under my belt. I have recently come across Clojure and while I don't have any opportunities to use it in production code, are there suggestions as to how I can apply the philosophy behind Clojure to my code in a more functional, declarative, data-oriented fashion in Java?

emccue19:09:00

Hey, so very quickly someone is going to show this book https://blog.klipse.tech/java/2021/03/05/data-oriented-programming-in-java.html Its not my favorite book but it kinda does try to directly answer that question

emccue19:09:05

The core problem with doing clojure design patterns in Java is that there is no way to be both typed and support code that does A -> A & B

emccue19:09:26

so in clojure it is common to do

(defn parse-json-body [request]
  (assoc request :json-body (parse-json (:body request))))
Which will take whatever is in map, add a new key, and return a new map

emccue19:09:52

the book basically goes in to how to do that with Map<Object, Object> - which is doable

emccue19:09:18

the part of Clojure design that is easier to adopt is to have immutable aggregates and use persistent data structures

emccue19:09:58

so that means using either the records language feature or Immutables the library to make immutable aggregates and libraries like vavr or pcollections to back them

emccue20:09:39

then use sealed interfaces to represent "or" type stuff

emccue20:09:33

sorry for the tiny messages, I need to actually "send" or I never get started and this is the result

emccue20:09:51

It might be more helpful if its interactive - so if you have some example Java code I can work through different ways to approach it

macrobartfast20:09:21

Straw poll: how are folks deploying their jars in production? CLI start? Application server? Something else?

cjohansen20:09:20

No jar, we just copy compiled sources and library jars into a docker container, and run it with java

cjohansen20:09:50

We use badigeon to output dependencies to a directory

lukasz20:09:33

uberjar in a Docker container, with a launcher script to pre load secrets from the secret store, set JVM flags etc

1
macrobartfast20:09:43

These look like cool approaches… do either of you use a docker repository or the like?

macrobartfast20:09:31

I like playing around a lot and creating quick setups so ready to deploy docker containers are appealing.

lukasz20:09:29

yeah, no way around it - all images are stored and pulled from ECR and deployed to ECS+Fargate

macrobartfast20:09:04

The alternative is something like Digital Ocean imaged droplets or Amazon images and so on but they are less universal and sometimes lead to costs to even store.

macrobartfast20:09:34

@U0JEFEZH6 right on that seems extra appealing, then.

macrobartfast20:09:58

Even for a partial hobby setup the time saved in deployment (since I tend to build and tear down prototype deployments frequently) offsets the infrastructural setup for a docker repo.

macrobartfast20:09:23

I may have a different view once I get through a full docker repo setup though, lol.

lukasz20:09:38

Maybe, if your setup is simple enough you can use GH's Docker repo for hosting and use docker compose on a single droplet

lukasz20:09:53

can work for small/POC/throwaway type of projects

macrobartfast20:09:57

I didn’t know they had one.

lukasz20:09:50

it's pretty neat as within a single repo you can have a CI pipeline (tests and building images) + storage of said images. Not sure about pricing tho

macrobartfast20:09:20

All this is basically what I’m after… quick POCs till one makes sense to run with, then deployment with some sort of monitoring and graceful restart (which I associate with typical Java application servers).

macrobartfast20:09:26

And single purpose docker images (i.e. microservices) but it’s been pointed out here that I should think more about namespacing and being smarter about my monoliths then relying on something really complicated to make up for my reasoning deficiencies.

lukasz20:09:22

Right, a single server deployment + Docker compose should give you most of that

macrobartfast20:09:22

But I’m definitely on the fence. The answer is probably ‘both’.

lukasz20:09:41

don't do microservices until you really have to

macrobartfast20:09:55

This is what everyone is telling me.

macrobartfast20:09:40

In fact, the book ‘Microservices With Clojure’ also suggests starting out with a monolith until you need microservices.

macrobartfast20:09:41

What about if a server falls over? How are you all notified? Does something automatically restart it?

lukasz20:09:35

We don't, ECS just creates a new node and starts again + we have metrics and logs automatically instrumenting our services. I haven't managed an EC2 instance in over 3 years now

macrobartfast20:09:42

Gotcha. I think I got turned off of Amazon at some point because of their growing complexity, but Digital Ocean lacks a lot of that.

lukasz20:09:23

It depends on your project, I'm working on a product that needs all of this stuff (and I'd still maintain that we have a relatively simple setup) - I wouldn't run it on DO, because it would actually be more complex to orchestrate all these containers, authentication, instrumentation and so on. Just like with microservices: do not use AWS until you have to (for sake of completeness - AWS has simpler deployment options like Elastic Beanstalk, Lambda, or app runner https://aws.amazon.com/apprunner/ - but you'll always have to deal with some degree of AWS' plumbing)

macrobartfast20:09:42

Yeah… I feel like I need to be somewhere between DO and AWS.

macrobartfast20:09:59

I was always drawn to OpenShift but it’s mainly hosted and very expensive.

macrobartfast20:09:16

These are really helpful links.

macrobartfast20:09:06

Some days I want to just try out an idea which requires a DB, a front-end, and so on and I want to get to seeing it work in minutes, not hours. Biff, the web framework, has been wonderful for this.

macrobartfast20:09:27

Well, it’s more than a web framework, which is what makes it great.

macrobartfast20:09:43

But for whatever reason I can’t stop thinking of unnecessarily (in most my cases) complex setups.

Jeongsoo Lee20:09:55

I made and implemented a DSL embedded in Clojure for writing mermaid.js sequence diagrams!

macrobartfast20:09:28

Nice! I’ve been wanting to start making ‘interactive’ diagrams… i.e. creating nodes generates infrastructure elements. This is helpful.

macrobartfast20:09:54

Or, more simply, interacting with a GUI that allows one to create these diagrams would update a model on the backend.

Jeongsoo Lee20:09:34

Hmm, that reminds me of GraphViz interactive online editor.

macrobartfast20:09:30

I’ll take a look.

walterl21:09:38

This is interesting, especially since I was using Mermaid just last week. 🙂 How does the DSL approach compare to something akin to a Hiccup-like markup approach?

Jeongsoo Lee21:09:40

Hiccup heavily uses plain (nested) vectors, while my approach uses function calls.

walterl21:09:50

Sure, but have you compared the pros and cons of the approaches? I ask because I recently made a similar DSL for Sieve scripts, and realized that a Hiccup-like markup approach works better for dynamic generation of output scripts. So I was wondering if you had a similar/different experience with Mermaid's code.

Jeongsoo Lee21:09:29

Hmm.. that’s certainly a food for thought. One shortcoming of the functional call approach might be that it is more difficult to add new keywords to the DSL if the target language gets a new one: e.g. if there is a new HTML tag <foo> announced by the Web Consortium, no breaking change to the DSL can be made if there is no set of whitelisted keywords used for validation: just use :foo for it.

Jeongsoo Lee21:09:50

However, like I just mentioned, that kind of flexibility requires an additional mechanism to be built in to the rendering function (`html` in Hiccup’s case), whereas the function-call approach does not since the available set of functions are predefined. So, invalid tokens are naturally unavailable, therefore this kind of validation is given for free.