19 comments

[ 1.8 ms ] story [ 45.5 ms ] thread
My understanding was that Go intentionally avoided patterns like this to improve readability.
Sometimes it feels like that Go mistakes readability for comprehendability. Sure, I can read every single line of Go I've ever read. But can I comprehend the bigger picture? Can I understand why? Isn't the actual meat buried under piles of non-abstraction?

This is precisely the premise for their library: I don't have the mental context to fit all the boilerplate in, nor do I have the brainpower to sift through it.

Sure, assembly is readable: every line is one machine instruction. But that's way too many details. On the other hand, C++ templates are not enough details.

It wasn’t to improve readability—it was for performance. There is no question what is happening in a triple-nested for loop. Once you start hiding it behind deeply-nested function calls, you get hidden performance penalties. If you have a functional language that is actually able to do some of this lazily, great, but that’s not Go.
I'm interested, but when I see a large coordination system sitting on top of any language's primitives, I'm immediately curious what kind of overhead it has. Please add some benchmarks and allocation reports.
I'm curious if someone could chime in on the state of adoption of these these Rx libraries in other language's ecosystems.

My poor memory seems to recall them gaining traction ~10 years ago, but they've fallen hard off my radar.

My fear with adopting a library like this for Go is actually that it might end up being very unfriendly to the profiler once bottlenecks start occurring.

This looks like it uses the unsafe library, proceed with caution
Think this looks beautiful, any idea as to the performance penalty vs say the new wg.Go(func({doSomething()}) sugared syntax in Go 1.25?
The explanation of RxGo being "wrong" or "out of order" seems very confusing? The case labeled "wrong" might aesthetically not look good, but from a data dependency perspective it seems totally valid to me. Why is it not valid to have the producer (map-A) resume and immediately do work "before" the consumer starts doing its work? Like, isn't that the point of breaking the "worker" bits up into separate functions? If I wanted the output of the `ro.Map()` example, wouldn't I just... not have map-A and map-B be separate functions?
The supposedly bad example is perfectly readable to anyone familiar with Go. A bit of refactoring into first class functions, and you'd have an easy to read, idiomatic, easy to test, well typed code base with obvious places to adjust useful behaviors like concurrency limits.

Meanwhile the samber/ro example is incomplete (Subscribe(...)?), and the source includes some weird stuff: https://github.com/samber/ro/blob/22b84c8296c01c4085e8913944...

Not to mention heaps of reflection and panics: https://github.com/samber/ro/blob/22b84c8296c01c4085e8913944...

The functionality and expressiveness might be fantastic, but I would evaluate it very carefully before use.

I love these "we invented erlang/elixir!" hn posts (1/3 of them?).

(snark aside, that syntax actually looks pretty nice).

It's really tough to read through a text where you can't go more than a few sentences without having something be described as *an adjective, a second adjective, and a third adjective*. Just let the code example speak for itself, man.

Speaking of the code examples, I am not convinced at all. The supposedly bad example is idiomatic and understandable to me, and I have a much better idea of what's going on than in the supposedly good examples. It contains the kind of constructions I expect to see when working on a Go codebase, and it will be easier to modify correctly for another Go developer coming in later. Please, work with the language instead of forcing it to wear a programming paradigm it doesn't like.

Decent idea, but much to big a library. Would have been better to just have the core primitives/plumbing w/o the library of all the different operators/filters/etc. Allow the user to implement the logic, use the library for the piping to hook them together.
I think this article does an alright job selling ro over RxGo, but doesn’t really explain why using a reactive library is better than plain go. The channel/goroutine example is fine, as they say, but they hand wave how this will fall apart in a more complex project. Conversely, their reactive example is mapping and filtering an 4 item array and handwave how the simplicity will remain no matter the size of the codebase.

I’ve worked in a few complex projects that adopted reactive styles and I don’t think they made things simpler. It was just as easy to couple components with reactive programming as it was without.

This is not the Go way. This abstraction appears to come for free, but it does not.
Thanks for this! I never used go but planning for a while and if I do so, I’d like to do it in a reactive way. As I’ve seen there are a few other reactive go libs as well, may I ask how this new lib relates to them?
I am sorry I might be a little naive here. The hardest thing about RxJava or RxJS was actually understanding the reactor pattern and then the nuances of threading (in Java) and how it made simple linear looking code callback hell. Go from ground up was built on promise of having the simple no-callback hell code that will be easy to read on eyes. Why do we keep going back to these pattern that over long term have proven to be hard to debug, thus hard to maintain well. Go is not even good at syntactic sugar like couple of other languages that might make it easy, why are people so excited about this? Should the reactivity and yielding of a go routine be taken care of under the hood?

Edit: I've maintained a full codebase with R2DBC and I can assure you many developers came scratching their heads to me sometimes on tell me why are we doing this again when we can only have finite connections to DB and those connections can be 1-1 mapped to worker threads.

Interesting read. Reactive programming fits well for complex, event-driven systems where Goroutines can get messy. It’s great to see more Go devs exploring patterns that improve concurrency control and data flow clarity.