Not surprised as Clojure is boring tech: slowly evolving with a big focus on stability (ie backwards compatibility).
Meanwhile: the core team has been extended the last couple of years. Also this summer NuBank (the company behind Clojure) announced the first 'Clojure Developer Advocate'. Their role will be to "focus on ways to support the existing Clojure community and grow the community through outreach and development."^1
I've been meaning to try this out, from my read it's a declarative way to get some structured concurrency. I work in a codebase that heavily uses core.async channels to manage concurrency and you really need to pay close attention to error handling. When you're spawning new threads you need to set up your own custom machinery to re-throw errors on a chans, close chans, and it looks like core.async.flow is a way to do all of this declaratively.
Just like `core.async` itself was a frontrunner of Virtual Threads on the JVM, I view `core.async.flow` as the Clojure version of the upcoming [0]Structured Concurrency JEP. I do wonder if it will use that under the hood once it becomes stable, the same way `core.async` is planning to move away from the `go` macro to just have it dispatch a virtual thread.
I think LISP is cool and want to use it more but I have 0 appetite to learn the toolchain and debug etc for JVM. You have Racket but Clojure ecosystem is already tiny.
Funny. I was just thinking about dismissing Clojure for a project I'm going to work on because I was concerned about it's lack of ability to work with async calls. I'm too used to how async in JavaScript and C#, and I'm not sure I'd want to work in an environment that doesn't have a simple way to structure async calls. It doesn't necessarily have to be async / await. Just some attention to the issue rather than completely ignoring it.
This is cool. So basically you create a group of threads and you can treat them as one unit. Trio does something similar (structured concurrency) with async functions for Python. Are these OS threads or green threads?
For me, the real 'click' moment with core.async wasn't just about replacing callbacks. It was realizing I could model entire business processes as a system of communicating channels. It completely changed how I think about system design.
typos (from a quick surface scan; I'm not familiar with Clojure or this package):
"The description arity takes the current state ..." should be "The transition arity takes the current state ..."
"excepiton" should be "exception"
"Exceptions thrown from the transition or transform arities, the exception ..." should be "If exceptions are thrown from the transition or transform arities, they ..."
Choose core.async.flow when you want a declarative, monitorable process graph over channels (pause/resume, error channel, flow monitor).
Choose Manifold when you need deferreds/streams as general primitives, precise per-op backpressure, error-aware chaining, or you’re in the Aleph/Netty stack.
very abstract but based on this, the answer is no.
This is really interesting, although I still can't get my head around the fact that core.async.flow topologies are immutable. I feel like most problems can't be solved with fixed topologies.
I guess one could in theory swap flows the same way values are swapped, but I wonder if this is the way this library is supposed to be used. I also wonder what happens to non-empty channel buffers in this case.
This reminded me of Elixir’s GenStage at first glance. Now I wonder how the underlying libs OTP and core.async relate conceptually and implementation wise.
Clojure(Script) is a mature, boring in the good sense language and ecosystem. It opened a whole subculture and dialects/ implementations like ClojureCLR, Babashka, nbb, Jank, Janet, Fennel, Joker, Basilisp, Hy, Clojerl and at least a few others I forgot to mention that can perhaps be found on this list: https://github.com/chr15m/awesome-clojure-likes
We are building apps for our clients in it, we will also have our own product built with Clojure and ClojureScript soon.
I recently used c.a.flow for a program that reads files from s3, massages the data a bit, and writes to a database. The code came out very easy to understand. The challenges were:
- getting back pressure right. You need to tune the buffers for the input chans correctly, which takes some thinking, and in my case, some blowing up in prd.
- flow monitor (the UI for debugging flows) can't handle large process states, which makes it pretty much useless for my program.
- understanding the flow as a whole (rather than specific processes, which are easy to understand in isolation). For example, answering questions like "why hasn't this process received a message for a while?" was tricky.
17 comments
[ 3.1 ms ] story [ 39.1 ms ] threadMeanwhile: the core team has been extended the last couple of years. Also this summer NuBank (the company behind Clojure) announced the first 'Clojure Developer Advocate'. Their role will be to "focus on ways to support the existing Clojure community and grow the community through outreach and development."^1
[1]: https://building.nubank.com/clojure-developer-advocate-nuban...
EDIT: wording.
Just like `core.async` itself was a frontrunner of Virtual Threads on the JVM, I view `core.async.flow` as the Clojure version of the upcoming [0]Structured Concurrency JEP. I do wonder if it will use that under the hood once it becomes stable, the same way `core.async` is planning to move away from the `go` macro to just have it dispatch a virtual thread.
[0]https://openjdk.org/jeps/453
"The description arity takes the current state ..." should be "The transition arity takes the current state ..."
"excepiton" should be "exception"
"Exceptions thrown from the transition or transform arities, the exception ..." should be "If exceptions are thrown from the transition or transform arities, they ..."
"provded" should be "provided"
EDIT:
gpt says,
Choose core.async.flow when you want a declarative, monitorable process graph over channels (pause/resume, error channel, flow monitor).
Choose Manifold when you need deferreds/streams as general primitives, precise per-op backpressure, error-aware chaining, or you’re in the Aleph/Netty stack.
very abstract but based on this, the answer is no.
I guess one could in theory swap flows the same way values are swapped, but I wonder if this is the way this library is supposed to be used. I also wonder what happens to non-empty channel buffers in this case.
I am curious to hear other opinions.
We are building apps for our clients in it, we will also have our own product built with Clojure and ClojureScript soon.
- getting back pressure right. You need to tune the buffers for the input chans correctly, which takes some thinking, and in my case, some blowing up in prd.
- flow monitor (the UI for debugging flows) can't handle large process states, which makes it pretty much useless for my program.
- understanding the flow as a whole (rather than specific processes, which are easy to understand in isolation). For example, answering questions like "why hasn't this process received a message for a while?" was tricky.