Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Yeah, I had a fairly large (about a year of solo dev work) app that I maintained both Clojure and F# ports of, doing a compare and contrast of the various language strengths. One day I refactored the F# to be async, a change that affected like half the codebase, but was completed pretty mechanically via changing the core lines, then following the red squigglies until everything compiled again, and it basically worked the first time. I then looked at doing the same to the Clojure code, poked at it a couple times, and that was pretty much the end of the Clojure port.



Hey, so my my career path has been C# (many years) -> F# (couple years) -> Clojure (3 months). I understand multithreading primarily through the lens of async/await, and have been having trouble fully grokking the Clojure's multithreading. One of the commandments of async/await is don't block: https://blog.stephencleary.com/2012/07/dont-block-on-async-c...

Which is why the async monad tends to infect everything. Clojure, as far as I can tell so far, doesn't support anything similar to computation expressions. So I'm guessing your "poked at it a couple times" was something like calling `pmap` and/or blocking a future? All my multithreaded Clojure code quickly blocks the thread... and I can't tell if this is idiomatic or if there's a better way.


Not even. It was opening it, looking, realizing it would take a couple weeks, and going back to F#. I did this a couple times before fully giving up.

IIRC/IIUC, Clojure's async support is closer to Go's (I've never used go), in the form of explicit channels. Though you can wrap that in a monad pretty easily, which I did for fun one day (https://gist.github.com/daxfohl/5ca4da331901596ae376). But neither option was easy to port AFAICT before giving up.

Note it's possible that porting async functionality to Clojure may have been easier that I thought at the time. Maybe adding some channels and having them do their thing could have "just worked". I was used to async requiring everything above it to be async too. But maybe channels don't require that, and you can just plop them in the low level code and it all magically works. A very brief venture into Go since then has made me wonder about that.


Sounds more like you ran into a conflict of mental model and language feature, not necessarily that the language couldn’t achieve your goal simply.


Yeah, quite possible. I haven't worked on the project in ~six years and lost all context, but I'd revisit it and see if perhaps there was a simple solution if any of it was still current.


Well, I think I stumbled on this article way back when I was originally porting, and it looks like it still holds: https://martintrojer.github.io/clojure/2014/03/09/working-wi....

While core.async pays homage to go, it's simply not go, and it's harder to work with, and generally the changes are going to be more invasive, and looking around at modern resources, I don't see anything that indicates much has changed. So while I might have been more efficient if I'd had the go mental model, that was definitely not the only problem. Migrating was too much to do in my fairly large project, hunting and pecking at each instance I made an async call. Whereas with F# it was truly mechanical and hard to mess up, as I described above.


Multi-threaded code is normally not implemented in an async style, but instead is done where each thread of execution is synchronous.

Async style comes into play generally for languages that lack real threads, or as a way to manage callbacks (even if single threaded), or in order to wait for blocking IO without the need for a real thread.

So ya, it's idiomatic to use blocking to coordinate between different threads in Clojure, same as Java.

Java decided to work on making stackful coroutines instead of stackless like C#. That requires a lot more work, but should be coming eventually to Java. At that point, your "blocking" code in Clojure will no longer block a real thread, but a lightweight fiber instead. But patience is needed for it.

In the meantime, if you're dealing with non-blocking IO that operates with callback semantics or other callback style code, what you can do in Clojure to make working with that easier is use one of:

> core.async - https://github.com/clojure/core.async

> Promesa - https://github.com/funcool/promesa

> Missionary - https://github.com/leonoel/missionary

> Missionary's lower level coroutine lib - https://github.com/leonoel/cloroutine/blob/master/doc/02-asy...


Thanks for the reply - what you say makes a lot of sense. I watched Rich's talk on Async and was like... "cool so `core.async` follows this pattern right?!" ...not quite.

I'll check out your other links though, much appreciated. Also hearing that I should just be okay with blocking is well, good to hear explicitly.




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: