clojure-norway

eaj 2025-12-18T08:06:57.941779Z

God morgen!

hkjels 2025-12-18T08:32:19.155599Z

Morn!

teodorlu 2025-12-18T08:44:28.763749Z

god morgen!

leifericf 2025-12-18T08:56:42.341489Z

Morn!

teodorlu 2025-12-18T09:34:23.954099Z

Gjettekonkurranse: hvorfor funker ikke dette forsøket på å parallellisere? get-secret er den trege funksjonen, og tar cirka 700 ms.

(defn load-secrets []
  (->> (concat (map (fn [[k secret]] [k (future (get-secret secret))]) env-secrets)
               (map (fn [[k secret]] [k (future (get-secret secret {:base64? true}))]) base64-encoded-env-secrets))
       (map (fn [[k f]] [k @f]))
       (into {})))

🤩 1
2025-12-18T09:37:34.833769Z

hehe, den derefer i loopen

teodorlu 2025-12-18T09:50:03.341859Z

men det er jo ikke et problem i seg selv? Først fyr av én future for hvert element, deretter krev at alle er ferdig?

2025-12-18T09:52:49.419629Z

ah, søren og

2025-12-18T09:53:04.604879Z

eller, nei, det er ingen "først" her 🙂 lazyyy

🎯 1
2025-12-18T09:53:36.031659Z

så den ender vel i praksis opp med å kalle future og derefe i sekvens

teodorlu 2025-12-18T09:55:44.214459Z

Bingo! Jeg la en doall mellom (concat ,,,) og (map ,,,).

teodorlu 2025-12-18T10:17:42.037489Z

Etter litt refaktorering, synes jeg denne koden ble ganske så hyggelig

(defn gcloud-secrets-access-latest [secret]
  (->> ["gcloud secrets versions access latest --project REDACTED_PROJECT_ID!!!!!! --secret " secret]
       (shell-med-feilrapportering {:out :string})
       :out))

(defn load-secret [{:keys [name format]}]
  (let [raw (gcloud-secrets-access-latest name)]
    (case format
      :raw raw
      :base64 (.encodeToString (Base64/getEncoder) (.getBytes raw)))))

(defn load-secrets []
  (-> env-secrets
      (update-vals #(future (load-secret %)))
      (update-vals deref)))

🔥 1
gunnar 2025-12-18T10:30:31.390479Z

Nå er jeg ikke helt stø på slikt i Clojure, men i Java ville jeg tydd til en ExecutorService slik at jeg får en forhåndsbestemt threadpool. Hvordan håndterer man slikt typisk i Clojure?

2025-12-18T10:30:45.109749Z

core.async 😍

gunnar 2025-12-18T10:34:24.088529Z

selvfølgelig 🙂

2025-12-18T11:00:22.364969Z

sitter fortsatt i kroppen det foredaget fra EuroClojure i Krakow i sikkert 2015 eller når det var om en som jobbet i postvesenet i England elns og hadde et stykke sekvensiell og relativt "imperativ" kode for en integrasjon, og gikk igjennom diverse måter og gjøre det async på. Til slutt viste han core.async, og koden så i praksis helt lik ut, bare at den var wrappet med litt kall til core.async 😄 Makroer ftw

👍 1
2025-12-18T11:02:45.493789Z

"Logan Campbell, Clojure at a Post Office" heter talken, men klarer ikke finne noe opptak eller slides

teodorlu 2025-12-18T11:47:38.401809Z

Mulig jeg er i særstilling her, men jeg har aldri vært i en posisjon hvor jeg har måttet bruke core.async skikkelig. Asynk er så ofte løst av "laget under meg", enten det er HTTP-serveren, databasen, tallknuse-biblioteket eller NATS. Jeg har tenkt en del på noe Rich sa i Language of the System: > Now we get to moving things around. I think it's one of the things in Clojure maybe I didn't make clear enough because I didn't need to wrap them is that the queues in java.util.concurrent are awesome. If you're not using them as part of your system designs internally, you're missing out. And, in the large, queues also rule because they have this really great characteristic. They're completely decoupling. [<https://github.com/matthiasn/talk-transcripts/blob/master/Hickey_Rich/LanguageSystem.md > |kilde>] Core.async.flow ble trukket ut fra / inspirert av hvordan Datomic er laget internt. Og jeg vil jo ha alt i løst koblede funksjoner! Enten ta inn data og gi data, eller gjør noe greier med køer hvis vi ikke kan "gjøre oss helt ferdig" før vi returnerer.

hypirion 2025-12-18T11:53:12.186919Z

Svaret er fordi du ikke bruker Claypoole 😊

teodorlu 2025-12-18T12:03:17.632719Z

fordi hvis jeg bruke Claypoole, ville jeg fått problemer som ville gjort at jeg måtte brukt core.async? trollface Spøk tilside, jeg er fullt klar over at jeg har ting å lære her. Men så mye av det jeg leser er tekniske demoer! "Se, du kan gjøre $OPPGAVE med $NYTT_VERKTØY!!!". Men jeg driver ikke og samler verktøy! Jeg prøver å finne ut av hvilke problemer jeg ikke ennå har kontroll på fordi jeg ikke behersker en gitt teknikk!

msolli 2025-12-18T12:35:11.237469Z

> Concat, the lazily-ticking time bomb https://stuartsierra.com/2015/04/26/clojure-donts-concat/

💥 1
gunnar 2025-12-18T12:37:54.571209Z

Det er endel overraskelser i clojure sin verktøykasse. Sånn sett, og med det du sier i bakhodet, @teodorlu, så bør man gripe etter egnet verktøy til jobben. Når det gjelder concurrency så bør man åpenbart styre unna fallgruvene som er lazy collections og kanskje heller bruke noe formålsrettet (som f.eks. ExecutorService - veldig lesbart, ingen tvil om hva som kommer til å skje)

teodorlu 2025-12-18T13:29:20.111839Z

lazy-seqs traff oss her ja, men ville du foretrukket ExecutorService over disse tre linjene?

(-> env-secrets
    (update-vals #(future (load-secret %)))
    (update-vals deref))
Spør av nysgjerrighet!

teodorlu 2025-12-18T13:34:14.017469Z

Vet heller ikke hvordan det hadde blitt med ExecutorService? future ser ut til å hente maks 32 om gangen. Vi har 9, så alt skjer samtidig.

gunnar 2025-12-18T13:49:50.324619Z

Det er kanskje litt av problemet med future, at det er udefinert hvor mange tråder som benyttes? Det spiller kanskje liten rolle hvis du vet at du har få samtidige oppgaver, men det hele kommer vel an på hva du har tilgjengelig av ressurser og om du kan ende opp med å blokkere andre deler av appliksjonen eller andre kontekstuelle påvirkninger. Aner ikke hvordan det ville sett ut med direkte bruk av ExecutorService i clojure - det vil helt sikkert ikke bli pent. Noe slikt som

(map #(.get %) (.invokeAll (Executors/newFixedThreadPool 4) (map load-secret env-secrets)))
(... uten at jeg vet om dette funker i det hele tatt - bare ren kladding fra hodet, og ingen som helst error-handling)

👍 1
2025-12-18T13:50:19.420219Z

enig, liker executors når man ønsker å ha en viss kontroll over runtimen sin

teodorlu 2025-12-18T13:59:14.768469Z

Eksplisitt gir mening! Jeg gjorde faktisk et forsøk på å få systemet mitt med å tryne ved å starte flere og flere futures, og klarte ikke å observere noen effekt på systemet. Lagde ca 50 000, mener jeg. @sardtok sa at på 1.12, skal future bruke virtual threads. Så da når jeg kanskje aldri noe tak? 🤷 Korreksjon: future bruker ikke virtual threads på 1.12. Det må man gjøre selv. Så alle dere som var bekymret for thread pools (for 9 kall til gcloud secrets) kan fortsette å være bekymret! 😄

2025-12-18T14:06:14.309069Z

ah nice, det hjelper jo veldig ja. Da blir det plutselig litt mindre viktig hvor mange tråder poolen bruker og sånt

msolli 2025-12-18T14:17:11.334489Z

future bruker samme thread pool som send-off. Den kan kontrolleres med set-agent-send-off-executor!:

(import '[java.util.concurrent Executors])

(def custom-pool (Executors/newFixedThreadPool 32))
(set-agent-send-off-executor! custom-pool)
Default er en unbounded, cached thread pool: https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Agent.java#L53

1
gunnar 2025-12-18T14:20:23.336679Z

Dette var nyttig informasjon! (fra alle sammen) og send-off kjente jeg ikke til

msolli 2025-12-18T14:23:45.449379Z

send-off hører til agents, som jeg aldri har brukt på 8 år med Clojure. Noen andre som har brukt agents, og til hva?

2025-12-18T14:26:39.798509Z

er veldig bra hvis du skal livekode ant colony simulators ihvertfall 🙂

😂 2
teodorlu 2025-12-18T14:27:42.675879Z

clojure-featuren som kun ble brukt til Rich sin salgspitch om at concurrency er fint med Clojure!

2025-12-18T14:29:44.684699Z

samme med software transactional memory, fet idé men man trengte det kanskje ikke i praksis?

2025-12-18T14:30:01.564459Z

aner ikke jeg altså, men det var kanskje Rich sin C++-erfaring som ville lage verktøy som var umulig å få til med C++ med relativt trivielt i Clojure

msolli 2025-12-18T14:30:09.067099Z

Ikke sant, det er det eneste eksempelet jeg har hørt om hvor agents er brukt! (Utenfor Clojure-bøker.)

quoll 2025-12-18T14:44:45.506599Z

(I'm still too scared to use my broken Norwegian, so please excuse the English) My first port of call for this would have been pmap on each of the two. But because I'd expect the cores to be saturated, then I'd probably do something fancy to concat the 2 sequences and use the pmap over all of them:

(defn load-secrets []
  (let [simple-count (count env-secrets)]
    (->> (pmap (fn [[k secret] idx]
                 (if (< idx simple-count)
                   (get-secret secret)
                   (get-secret secret {:base64? true})))
               (concat env-secrets base64-env-secrets)
               (range))   
         (into {})))
core.async can definitely fine-tune thread usage much better, so it would improve things much more, especially if you're on a JVM that can use lightweight threads.

quoll 2025-12-18T15:10:17.687569Z

However, this continues to demonstrate the same problem: one future per operation. There are a couple of libraries that reduce this overhead, like https://github.com/clj-commons/claypoole.

(require '[com.climate.claypoole :as cp])
(defn load-secrets []
  (let [simple-count (count env-secrets)]
    (->> (cp/pmap (fn [[k secret] idx]
                    (if (< idx simple-count)
                      (get-secret secret)
                      (get-secret secret {:base64? true})))
                  (concat env-secrets base64-env-secrets)
                  (range))   
         (into {})))

teodorlu 2025-12-18T17:44:53.190539Z

It appears I incorrectly assumed future used virtual threads! Thanks for the code examples 🙏

quoll 2025-12-18T17:47:32.679549Z

I thought about it a bit more, and virtual threads are not going to help, unless your get-secret function has I/O. Virtual threads let you do more work when any of the threads need to wait for things (which is usually defined by I/O).

teodorlu 2025-12-18T18:01:16.926899Z

get-secret is all IO! It shells out to gcloud, which sits and waits for the network. Implementation after refactor can be seen earlier in this thread: https://clojurians.slack.com/archives/C061XGG1W/p1766053062037489?thread_ts=1766050463.954099&amp;cid=C061XGG1W

teodorlu 2025-12-18T18:01:54.174929Z

… where shell-med-feilrapportering is a wrapper around babashka.process/shell with some decisions about error reporting.

gunnar 2025-12-19T06:57:02.384349Z

> Virtual threads let you do more work when any of the threads need to wait for things (which is usually defined by I/O). I guess virtual threads are for Java what async/await is to js (and async/task to c#), but with the same programming model as with regular threads. So just async tasks, not necessarily concurrent tasks - or can virtual threads also be run concurrently by an executor in some cases? maybe they have to be "promoted" to full threads in those cases?

Sardtok 2025-12-19T09:18:52.887169Z

Virtual threads have low overhead, so you can create a huge amount of them without worrying about exhausting the OS thread limits. They aren't directly dependent on OS scheduling like an OS thread, but handled by relatively lightweight code in the JVM. Under the hood they are run on threads in a ForkJoinPool.

2025-12-19T09:21:42.239779Z

with threads, you'll get an overflow of stacks 😎

quoll 2025-12-19T11:16:06.886179Z

Yes, you can create an enormous number of virtual threads without much overhead. (So using a “Future” per work item is less expensive. This is why I thought of them), but until @teodorlu said that it was mostly IO then scheduling batches of operations gets better CPU performance. However, IO bounding makes virtual threads perfect.

Sardtok 2025-12-19T11:41:20.608749Z

In this specific case, it is just a small Babashka task that runs when launching the dev environment, to ensure we have the right keys for various external systems. That way we can test things like various identity providers and APIs locally. They aren't super sensitive, because they are configured for test environments at the providers, but this way we could open source the code at some point without leaking any keys. They are also generated by our platform, so GCP secrets happen to be where they end up for now. The Google Cloud CLI is just super slow at getting a single key. It can take up to two seconds for one. So starting the dev environment takes a few more seconds than necessary. Sure, it's not that much, but enough to be a bit annoying if you have to restart it at some point.

quoll 2025-12-19T12:16:48.044279Z

This reminds me of what core.async https://swannodette.github.io/2013/07/12/communicating-sequential-processes/ 12 years ago 🤯

quoll 2025-12-19T12:19:11.905849Z

Then David did 10,000 go blocks https://swannodette.github.io/2013/08/02/100000-processes/

gunnar 2025-12-19T12:25:55.359169Z

Isn't concurrent the wrong word here? In David's example none of the code will be executed at the same time, but rather one after the other.

teodorlu 2025-12-19T12:26:24.453199Z

"concurrent" is right — it is concurrent, but not parallell! Paralell means that it actually runs at the same time.

gunnar 2025-12-19T12:28:28.058029Z

aha!

quoll 2025-12-19T12:28:31.041909Z

Concurrent is like a time-sharing OS on a single CPU… everything runs concurrently, but only one process or thread is active at a time. The OS uses system calls as an opportunity to pre-empt the process and schedule the next one.

quoll 2025-12-19T12:28:51.696569Z

Of course, modern OSes are both concurrent AND parallel

quoll 2025-12-19T12:29:11.862869Z

we get the "worst of both worlds" 😜

quoll 2025-12-19T12:30:19.059499Z

core.async uses macros to do the same thing. Which continues to blow my mind to this day

➕ 1
teodorlu 2025-12-19T12:30:50.224849Z

yeah — the go macro is a beast. Insane stuff.

teodorlu 2025-12-19T12:32:05.600089Z

a thousand lines of dragons. https://github.com/clojure/core.async/blob/7cc715ac25b4a0f232a7fe1049f90f14ef10fd96/src/main/clojure/clojure/core/async/impl/go.clj#L1044 Edit: more than a thousand lines, it uses other namespaces too 😅

teodorlu 2025-12-19T12:32:16.125739Z

(both records and multimethods internally)

quoll 2025-12-19T12:32:20.385889Z

Java 1.0 had "green threads" as well as native threads. Green threads did the same thing in the JVM. Indeed, Virtual threads follow exactly the same principle… but done properly (Green threads were terrible)

quoll 2025-12-19T12:37:00.858069Z

(Incidentally, this is something that bugs me about JavaScript. JavaScript does not do this. Instead, you have to explicitly acknowledge that you're giving up control, and write code to be executed when the JS engine returns to you, via a .this block. This paradigm does offer the ability to do more operations before returning, but in practice I've never anyone take advantage of it. I wish JS just did pre-emption like every other OS/VM since the late '60s, but at least core.async did it for us)

teodorlu 2025-12-19T12:43:05.183639Z

I'm curious about whether Zig's "IO as an explicit parameter" approach will be fruitful. They decided to rewrite the compiler and break all userspace IO code to get what they considered to be a clean solution. Async code now needs to "take an IO". The promise is application authors can decide how to do IO (async, sync, etc), and library authors can be IO strategy agnostic, by leveraging the passed parameter.

quoll 2025-12-19T12:55:22.453269Z

I do need to spend time with Zig. I listen to "Software Unscripted", and even though Richard develops Roc, he refers to Zig all the time. It sounds fascinating.

teodorlu 2025-12-19T13:45:59.700859Z

I've tried it a bit. It strikes me as very high quality tech. However, every time I try, I'm reminded about how nice it is to use a high-level language like Clojure, and how nice our data structures are. I don't really care about memory when I'm doing information programming in non-performance-bottleneck situations. My colleague @mathias.iversen487 has ventured further down the rabbit hole than I have. A month or so ago, he presented https://github.com/boosja/zlides for us. It's a terminal slideshow viewer. Nice, little application, that's a good fit for Zig.

👍 1
👋 1
quoll 2025-12-19T14:35:03.875119Z

I guess I'm curious because I've been spending so much time in C++ recently

quoll 2025-12-19T14:35:34.661699Z

CUDA and Metal basically require it (that's not completely true, but mostly)

👍 1
teodorlu 2025-12-19T15:26:54.863919Z

gpu programming pulls at me too! It's one reason I'm interested in Zig.

msolli 2025-12-18T12:45:15.597279Z

Ingen grunn til å vente med denne: https://www.meetup.com/clojure-oslo/events/312486619/ 🍛🍛🍛 Clojure-lønsj tirsdag 13. januar kl. 12 🍛🍛🍛 Håper vi ses! Og god jul i mellomtiden!

💯 8
🎄 5
teodorlu 2025-12-18T13:56:10.225669Z

god jul, Martin!

🎄 1
Zeniten 2025-12-18T15:32:18.548559Z

god jul, Teodor!

😊 1
slipset 2025-12-18T17:48:19.540799Z

God jul!

Zeniten 2025-12-18T16:10:00.421579Z

https://dl.acm.org/openaccess \o/

💯 1
🎉 3
teodorlu 2025-12-18T17:20:52.952599Z

vitenskap bør være åpen for alle! 🙌

gunnar 2025-12-18T05:15:48.621699Z

Morn!

slipset 2025-12-18T06:25:33.071309Z

Mrn.,

hypirion 2025-12-18T06:30:59.460599Z

Morn!

mokr 2025-12-18T07:40:47.522099Z

Morn!

emil0r 2025-12-18T07:42:01.808469Z

Morn

cjohansen 2025-12-18T07:57:25.554729Z

Morn!